mirror of
https://github.com/n0mjs710/HBlink4.git
synced 2026-11-08 04:19:26 +00:00
Make the module files directly runnable again; no install step
The previous commit required "pip install -e ." before anything would run, and made "python hblink4/hblink.py" fail outright. That trades a real support burden -- an install step people forget after git pull, and an error message that looks like a broken checkout -- for an internal correctness property. Not a good trade for this project's users, who have run hblink by interpreter-plus-path since HBlink3 and DMRlink. Establish the package context explicitly instead. When hblink4/hblink.py or dashboard/server.py is run by path, __package__ is empty, so each now puts the project root on sys.path and sets __package__ before its relative imports. That is enough for "from .constants import ..." to resolve to hblink4.constants -- one copy, not the two the old except-ImportError fallback produced. Importing the parent package is cheap because __init__.py is metadata only. Every invocation style now works and none of them double-load: python hblink4/hblink.py config/config.json <- systemd uses this python dashboard/server.py <- systemd uses this python run_hblink.py config/config.json <- convenience python run_dashboard.py <- convenience python -m hblink4.hblink hblink4 <- if pip installed The systemd units invoke the module files directly rather than going through a launcher, so there is one less moving part between systemd and the program. run_hblink.py and run_dashboard.py stay in the project root as launchers for interactive use, but they are now genuinely logic-free: the uvicorn call that used to live in run_dashboard.py is dashboard.server.run(). requirements.txt and requirements-dashboard.txt come back as the documented install path. pyproject.toml stays for those who want "pip install -e .", but is now optional and reads its dependencies from those files so the two cannot drift. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6a644a818c
commit
f1a2e4133c
15 files changed
+126
-67
No files matched your search
@@ -50,13 +50,14 @@ except ImportError:
|
||||
```bash
|
||||
# Development setup
|
||||
python3 -m venv venv && source venv/bin/activate
|
||||
pip install -e ".[dashboard,dev]" # editable install + console scripts
|
||||
pip install -r requirements.txt
|
||||
pip install -r requirements-dashboard.txt
|
||||
|
||||
# Run server only
|
||||
hblink4 [config/config.json] # console script from pip install -e .
|
||||
python run_hblink.py [config/config.json] # or: python hblink4/hblink.py ...
|
||||
|
||||
# Run dashboard only (bind/port from the 'web' section of dashboard/config.json)
|
||||
hblink4-dashboard [bind] [port]
|
||||
python run_dashboard.py [bind] [port] # or: python dashboard/server.py ...
|
||||
|
||||
# Run both (production-like)
|
||||
./run_all.sh
|
||||
@@ -105,7 +106,7 @@ target_repeater = user_cache.lookup_user(target_id)
|
||||
|
||||
- **Core logic**: `hblink4/` package with protocol handling
|
||||
- **Configuration**: `config/config.json` (server), `dashboard/config.json` (web)
|
||||
- **Entry points**: console scripts `hblink4` -> `hblink4.hblink:main` and `hblink4-dashboard` -> `dashboard.server:run`, declared in `pyproject.toml`; `run_all.sh` runs both for development. Never run the module files by path -- it loads the package twice.
|
||||
- **Entry points**: `hblink4/hblink.py` and `dashboard/server.py` are directly runnable (what systemd uses). `run_hblink.py` / `run_dashboard.py` in the root are logic-free convenience launchers; `run_all.sh` runs both. No install step required. `pip install -e .` additionally provides `hblink4` / `hblink4-dashboard` commands.
|
||||
- **Deployment**: Systemd services expect user ownership, virtual environment
|
||||
- **Data**: `logs/`, `dashboard/data/` for persistence
|
||||
- **Tests**: `tests/` with pytest, focus on access control and stream tracking
|
||||
|
||||
+8
-14
@@ -9,30 +9,24 @@ This directory contains systemd service files for running HBlink4 and its dashbo
|
||||
|
||||
## Upgrading an existing install
|
||||
|
||||
**The launcher scripts `run.py` and `run_dashboard.py` have been removed, and
|
||||
HBlink4 is now installed as a package with console-script entry points.** Older
|
||||
unit files point at files that no longer exist and **will fail on the next
|
||||
restart** — install the package and update the units before restarting anything.
|
||||
**The units now invoke the modules directly** instead of going through a
|
||||
launcher script, so there is one less moving part between systemd and the
|
||||
program. `run_hblink.py` and `run_dashboard.py` still exist in the project root
|
||||
as convenience launchers for interactive use. There is **no install step** —
|
||||
everything runs straight from the checkout, so a `git pull` needs nothing beyond
|
||||
a restart.
|
||||
|
||||
What changed in the units:
|
||||
|
||||
| | Old | New |
|
||||
|---|---|---|
|
||||
| `hblink4` `ExecStart` | `venv/bin/python .../run.py` | `venv/bin/hblink4 /home/cort/hblink4/config/config.json` |
|
||||
| `hblink4-dash` `ExecStart` | `venv/bin/python .../run_dashboard.py` | `venv/bin/hblink4-dashboard` (bind/port from `dashboard/config.json`) |
|
||||
| `hblink4` `ExecStart` | `venv/bin/python .../run.py` | `venv/bin/python .../hblink4/hblink.py .../config/config.json` |
|
||||
| `hblink4-dash` `ExecStart` | `venv/bin/python .../run_dashboard.py` | `venv/bin/python .../dashboard/server.py` (bind/port now from `dashboard/config.json`) |
|
||||
| `hblink4-dash` `WorkingDirectory` | `.../hblink4/dashboard` | `.../hblink4` |
|
||||
| both | `PrivateTmp=true` | *removed* — see [Security Features](#security-features) |
|
||||
|
||||
The entry points exist because running the module files by path puts the
|
||||
package's interior on `sys.path` and loads every module twice under two sets of
|
||||
names — the dual import trap. Install first, then swap the units:
|
||||
|
||||
```bash
|
||||
cd /home/cort/hblink4
|
||||
source venv/bin/activate
|
||||
pip install -e ".[dashboard]"
|
||||
ls -l venv/bin/hblink4 venv/bin/hblink4-dashboard # both should exist
|
||||
|
||||
sudo cp hblink4.service hblink4-dash.service /etc/systemd/system/
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl restart hblink4 hblink4-dash
|
||||
|
||||
+1
-1
@@ -81,7 +81,7 @@ The button appears as the first item in the header status area with a light blue
|
||||
|
||||
## Usage
|
||||
|
||||
The dashboard is started automatically with `./run_all.sh`, or separately with the `hblink4-dashboard` command (installed by `pip install -e ".[dashboard]"`). It binds the address and port from the `web` section of `dashboard/config.json` (default `0.0.0.0:8080`); passing `[bind] [port]` on the command line overrides that for one-off runs. Access at http://localhost:8080 (or your server IP for remote access).
|
||||
The dashboard is started automatically with `./run_all.sh`, or separately with `python3 run_dashboard.py` (or `python3 dashboard/server.py`) from the repository root. It binds the address and port from the `web` section of `dashboard/config.json` (default `0.0.0.0:8080`); passing `[bind] [port]` on the command line overrides that for one-off runs. Access at http://localhost:8080 (or your server IP for remote access).
|
||||
|
||||
## Dashboard Components
|
||||
|
||||
|
||||
+10
-7
@@ -21,13 +21,16 @@ from fastapi.staticfiles import StaticFiles
|
||||
from pathlib import Path
|
||||
import logging
|
||||
|
||||
try:
|
||||
from .user_db import UserDatabase, compute_next_refresh_seconds, _age_str
|
||||
except ImportError:
|
||||
# Running the dashboard directly without package install
|
||||
import sys as _sys
|
||||
_sys.path.insert(0, str(Path(__file__).parent))
|
||||
from user_db import UserDatabase, compute_next_refresh_seconds, _age_str
|
||||
# Allow this file to be run directly by path (python dashboard/server.py) as
|
||||
# well as imported. Establishing the package context means user_db is imported
|
||||
# once, as dashboard.user_db, rather than a second bare copy under sys.path
|
||||
# surgery.
|
||||
if __package__ in (None, ""):
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
__package__ = "dashboard"
|
||||
import dashboard # noqa: F401 (parent package must be in sys.modules)
|
||||
|
||||
from .user_db import UserDatabase, compute_next_refresh_seconds, _age_str
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
@@ -237,7 +237,7 @@ The `web` section is the **browser-facing web UI**:
|
||||
}
|
||||
```
|
||||
|
||||
`hblink4-dashboard [bind] [port]` overrides these for one-off runs; with no arguments the config values are used.
|
||||
`python3 run_dashboard.py [bind] [port]` overrides these for one-off runs; with no arguments the config values are used.
|
||||
|
||||
The `event_receiver` section is the **listening socket for the link from HBlink4**:
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ Type=simple
|
||||
User=cort
|
||||
Group=cort
|
||||
WorkingDirectory=/home/cort/hblink4
|
||||
ExecStart=/home/cort/hblink4/venv/bin/hblink4-dashboard
|
||||
ExecStart=/home/cort/hblink4/venv/bin/python /home/cort/hblink4/dashboard/server.py
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ Type=simple
|
||||
User=cort
|
||||
Group=cort
|
||||
WorkingDirectory=/home/cort/hblink4
|
||||
ExecStart=/home/cort/hblink4/venv/bin/hblink4 /home/cort/hblink4/config/config.json
|
||||
ExecStart=/home/cort/hblink4/venv/bin/python /home/cort/hblink4/hblink4/hblink.py /home/cort/hblink4/config/config.json
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
|
||||
+11
-4
@@ -32,10 +32,17 @@ LOGGER = logging.getLogger(__name__)
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Package-relative imports only. There is deliberately no "except ImportError"
|
||||
# fallback to bare module names: that fallback let hblink.py be run by file path,
|
||||
# which loads this package's modules twice under two sets of names (see the note
|
||||
# in __init__.py). Run the installed console script, or python -m hblink4.hblink.
|
||||
# Allow this file to be run directly by path (python hblink4/hblink.py) as well
|
||||
# as imported. Establishing the package context up front means the relative
|
||||
# imports below resolve to hblink4.* exactly once -- the older approach of
|
||||
# falling back to bare "from constants import ..." loaded every module a second
|
||||
# time under a second name (see the note in __init__.py). Cheap: __init__.py is
|
||||
# metadata only, so importing the parent package pulls in nothing else.
|
||||
if __package__ in (None, ""):
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
__package__ = "hblink4"
|
||||
import hblink4 # noqa: F401 (parent package must be in sys.modules)
|
||||
|
||||
from .constants import (
|
||||
RPTA, RPTL, RPTK, RPTC, RPTCL, MSTCL, DMRD,
|
||||
MSTNAK, MSTPONG, RPTPING, RPTACK, RPTP, RPTO, DMRA
|
||||
|
||||
+9
-18
@@ -9,24 +9,13 @@ readme = "readme.md"
|
||||
requires-python = ">=3.9"
|
||||
license = "GPL-3.0-or-later"
|
||||
authors = [{ name = "Cort Buffington, N0MJS" }]
|
||||
dynamic = ["version"]
|
||||
|
||||
dependencies = [
|
||||
"typing-extensions>=4.7.1",
|
||||
"dmr_utils3>=0.1.29",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
# The dashboard is a separate process; install with: pip install -e ".[dashboard]"
|
||||
dashboard = [
|
||||
"fastapi>=0.104.0",
|
||||
"uvicorn[standard]>=0.24.0",
|
||||
"websockets>=12.0",
|
||||
]
|
||||
dev = [
|
||||
"pytest>=7.4.0",
|
||||
"pytest-cov>=4.1.0",
|
||||
]
|
||||
# Installing is OPTIONAL. HBlink4 runs straight from a git checkout:
|
||||
# pip install -r requirements.txt -r requirements-dashboard.txt
|
||||
# python hblink.py config/config.json
|
||||
# This file exists for people who prefer `pip install -e .`, which additionally
|
||||
# provides `hblink4` / `hblink4-dashboard` commands. Dependencies are read from
|
||||
# the requirements files so there is only one place to declare them.
|
||||
dynamic = ["version", "dependencies", "optional-dependencies"]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/n0mjs710/HBlink4"
|
||||
@@ -39,6 +28,8 @@ hblink4-dashboard = "dashboard.server:run"
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
version = { attr = "hblink4.__version__" }
|
||||
dependencies = { file = ["requirements.txt"] }
|
||||
optional-dependencies.dashboard = { file = ["requirements-dashboard.txt"] }
|
||||
|
||||
[tool.setuptools]
|
||||
packages = ["hblink4", "dashboard"]
|
||||
|
||||
@@ -53,13 +53,14 @@ python3 -m venv venv
|
||||
source venv/bin/activate # On Windows: venv\Scripts\activate
|
||||
```
|
||||
|
||||
3. Install HBlink4 and its dependencies:
|
||||
3. Install requirements:
|
||||
```bash
|
||||
pip install -e ".[dashboard]" # add ,dev for the test suite
|
||||
pip install -r requirements.txt
|
||||
pip install -r requirements-dashboard.txt # if you want the dashboard
|
||||
```
|
||||
|
||||
This installs the package in editable mode and creates two commands in the
|
||||
virtual environment: `hblink4` and `hblink4-dashboard`.
|
||||
HBlink4 runs straight from the checkout — there is no install step, and
|
||||
nothing to redo after a `git pull`.
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -80,14 +81,21 @@ For production deployments with automatic startup, see [SYSTEMD.md](SYSTEMD.md).
|
||||
# Start all services together
|
||||
./run_all.sh
|
||||
|
||||
# Or start services separately (with the venv activated):
|
||||
hblink4 config/config.json # HBlink4 server
|
||||
hblink4-dashboard # Web dashboard (in another terminal)
|
||||
# Or start services separately (from the repository root):
|
||||
python3 run_hblink.py config/config.json # HBlink4 server
|
||||
python3 run_dashboard.py # Web dashboard (in another terminal)
|
||||
```
|
||||
|
||||
Both read their configuration from disk: the server's config path defaults to `config/config.json`, and the dashboard takes its bind address and port from the `web` section of `dashboard/config.json`. Each can be overridden on the command line — `hblink4 <config>` and `hblink4-dashboard [bind] [port]` — for one-off runs.
|
||||
`run_hblink.py` and `run_dashboard.py` are convenience launchers containing no
|
||||
logic. The modules they point at run just as well on their own, which is what
|
||||
the systemd units use — no venv activation needed:
|
||||
|
||||
Run the modules by file path and Python loads the package twice under two sets of names; use these commands, or `python3 -m hblink4.hblink`.
|
||||
```bash
|
||||
/path/to/hblink4/venv/bin/python /path/to/hblink4/hblink4/hblink.py /path/to/hblink4/config/config.json
|
||||
/path/to/hblink4/venv/bin/python /path/to/hblink4/dashboard/server.py
|
||||
```
|
||||
|
||||
Both read their configuration from disk: the server's config path defaults to `config/config.json`, and the dashboard takes its bind address and port from the `web` section of `dashboard/config.json`. Each accepts the same overrides on the command line for one-off runs.
|
||||
|
||||
Access the dashboard at http://localhost:8080. See [Dashboard Documentation](dashboard/README.md) for features and configuration.
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# HBlink4 Dashboard Requirements
|
||||
# Install with: pip install -r requirements-dashboard.txt
|
||||
|
||||
# Web framework
|
||||
fastapi>=0.104.0
|
||||
|
||||
# ASGI server with WebSocket support
|
||||
uvicorn[standard]>=0.24.0
|
||||
|
||||
# WebSocket support (included in uvicorn[standard])
|
||||
websockets>=12.0
|
||||
@@ -0,0 +1,6 @@
|
||||
# asyncio is part of Python 3.7+ standard library (no external dependency needed)
|
||||
# Removed: twisted>=22.10.0, pyOpenSSL>=23.2.0, service_identity>=23.1.0
|
||||
typing-extensions>=4.7.1 # For Python <3.9 compatibility
|
||||
pytest>=7.4.0 # For running tests
|
||||
pytest-cov>=4.1.0 # For test coverage reports
|
||||
dmr_utils3>=0.1.29 # For DMR FEC decoding and LC extraction
|
||||
+7
-7
@@ -15,7 +15,7 @@ NC='\033[0m' # No Color
|
||||
|
||||
# Check if virtual environment exists
|
||||
if [ ! -d "venv" ]; then
|
||||
echo -e "${YELLOW}Virtual environment not found. Please run: python3 -m venv venv && source venv/bin/activate && pip install -e \".[dashboard]\"${NC}"
|
||||
echo -e "${YELLOW}Virtual environment not found. Please run: python3 -m venv venv && source venv/bin/activate && pip install -r requirements.txt${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -23,10 +23,10 @@ fi
|
||||
echo -e "${BLUE}Activating virtual environment...${NC}"
|
||||
source venv/bin/activate
|
||||
|
||||
# Check that the package (and its dashboard extra) is installed
|
||||
if ! command -v hblink4-dashboard >/dev/null 2>&1; then
|
||||
echo -e "${YELLOW}HBlink4 not installed in the venv. Installing...${NC}"
|
||||
pip install -e ".[dashboard]"
|
||||
# Check if dashboard requirements are installed
|
||||
if ! python3 -c "import fastapi" 2>/dev/null; then
|
||||
echo -e "${YELLOW}Dashboard dependencies not found. Installing...${NC}"
|
||||
pip install -r requirements-dashboard.txt
|
||||
fi
|
||||
|
||||
# Function to cleanup on exit
|
||||
@@ -44,7 +44,7 @@ echo
|
||||
|
||||
# Start dashboard in background
|
||||
echo -e "${BLUE}Starting Dashboard (bind/port from dashboard/config.json)...${NC}"
|
||||
hblink4-dashboard &
|
||||
python3 run_dashboard.py &
|
||||
DASHBOARD_PID=$!
|
||||
|
||||
# Give dashboard time to start
|
||||
@@ -52,7 +52,7 @@ sleep 2
|
||||
|
||||
# Start HBlink4 server
|
||||
echo -e "${BLUE}Starting HBlink4 server...${NC}"
|
||||
hblink4 config/config.json &
|
||||
python3 run_hblink.py config/config.json &
|
||||
HBLINK_PID=$!
|
||||
|
||||
echo
|
||||
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Convenience launcher for the HBlink4 dashboard.
|
||||
|
||||
python run_dashboard.py [bind] [port]
|
||||
|
||||
Purely a shortcut for interactive use -- it contains no logic beyond locating
|
||||
dashboard/server.py, which is equally runnable on its own:
|
||||
|
||||
python dashboard/server.py [bind] [port]
|
||||
|
||||
The systemd unit uses that direct path rather than this script. With no
|
||||
arguments the bind address and port come from the 'web' section of
|
||||
dashboard/config.json.
|
||||
"""
|
||||
|
||||
from dashboard.server import run
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Convenience launcher for the HBlink4 server.
|
||||
|
||||
python run_hblink.py [config/config.json]
|
||||
|
||||
Purely a shortcut for interactive use -- it contains no logic beyond locating
|
||||
hblink4/hblink.py, which is equally runnable on its own:
|
||||
|
||||
python hblink4/hblink.py [config/config.json]
|
||||
|
||||
The systemd unit uses that direct path rather than this script.
|
||||
"""
|
||||
|
||||
from hblink4.hblink import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in new issue
Block a user