Compare commits

...

11 commits
v0.1.1 ... main

Author SHA1 Message Date
581d7edb9f Remove rich dep
All checks were successful
/ build (push) Successful in 14s
2025-05-25 17:34:05 +02:00
7d1af1199c Cosmetic
All checks were successful
/ build (push) Successful in 16s
2025-05-21 18:26:22 +02:00
c821291546 Cosmetic
All checks were successful
/ build (push) Successful in 16s
2025-05-21 18:23:55 +02:00
43137c4004 Cosmetic
All checks were successful
/ build (push) Successful in 3s
2025-05-21 18:03:45 +02:00
c4b59bfa14 Cosmetic
All checks were successful
/ build (push) Successful in 4s
2025-05-21 18:02:42 +02:00
7de3ac9e6a Cosmetic
All checks were successful
/ build (push) Successful in 5s
2025-05-21 18:01:31 +02:00
04279ce8c6 Get device info with a device_id or WAN MAC address
All checks were successful
/ build (push) Successful in 15s
2025-05-06 01:35:57 +02:00
4cd86bfbb0 Rename python package
All checks were successful
/ build (push) Successful in 16s
2025-05-05 10:52:54 +02:00
d87fc4691b CI: fix organisation in pypi
All checks were successful
/ build (push) Successful in 15s
2025-05-05 10:48:38 +02:00
0f18d52588 CI: add containerfile
All checks were successful
/ build (push) Successful in 16s
2025-05-05 10:23:18 +02:00
7e7b974941 CI: build package
Some checks failed
/ build (push) Failing after 4s
2025-05-05 05:13:42 +02:00
9 changed files with 150 additions and 62 deletions

1
.containerignore Normal file
View file

@ -0,0 +1 @@
.venv

View file

@ -0,0 +1,82 @@
on:
push:
workflow_dispatch:
inputs:
verbose:
description: "Verbose"
required: false
default: false
type: boolean
jobs:
build:
runs-on: container
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install the latest version of uv
uses: astral-sh/setup-uv@v4
with:
version: "0.7.2"
- name: Install
run: uv sync
- name: Get version
run: echo "VERSION=$(.venv/bin/dunamai from any --style semver)" >> $GITHUB_ENV
- name: Version
run: echo $VERSION
- name: Get distance from tag
run: echo "DISTANCE=$(.venv/bin/dunamai from any --format '{distance}')" >> $GITHUB_ENV
- name: Distance
run: echo $DISTANCE
- name: Workaround for bug of podman-login
if: env.DISTANCE == '0'
run: |
mkdir -p $HOME/.docker
echo "{ \"auths\": {} }" > $HOME/.docker/config.json
- name: Log in to the container registry (with another workaround)
if: env.DISTANCE == '0'
uses: actions/podman-login@v1
with:
registry: ${{ vars.REGISTRY }}
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_PASSWORD }}
auth_file_path: /tmp/auth.json
- name: Build the container image
if: env.DISTANCE == '0'
uses: actions/buildah-build@v1
with:
image: gacsco
oci: true
labels: gacsco
tags: "latest ${{ env.VERSION }}"
containerfiles: |
./Containerfile
- name: Push the image to the registry
if: env.DISTANCE == '0'
uses: actions/push-to-registry@v2
with:
registry: "docker://${{ vars.REGISTRY }}/${{ vars.ORGANISATION }}"
image: gacsco
tags: "latest ${{ env.VERSION }}"
- name: Build wheel
if: env.DISTANCE == '0'
run: uv build --wheel
- name: Publish Python package (home)
if: env.DISTANCE == '0'
env:
LOCAL_PYPI_TOKEN: ${{ secrets.LOCAL_PYPI_TOKEN }}
run: uv publish --publish-url https://code.philo.ydns.eu/api/packages/K-Net/pypi --token $LOCAL_PYPI_TOKEN
continue-on-error: true

14
Containerfile Normal file
View file

@ -0,0 +1,14 @@
# Build: podman build -t code.philo.ydns.eu/philorg/tinyseady-mailer -f Containerfile
FROM docker.io/python:3.13-alpine
RUN apk add --no-cache git
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/
COPY . /app
# Sync the project into a new environment, using the frozen lockfile
WORKDIR /app
RUN uv pip install --system .
ENTRYPOINT ["gacsco"]

View file

@ -0,0 +1,17 @@
# GenieACS COmmander
[GenieACS](https://github.com/genieacs/genieacs) is an open source TR-069
management tool for remote control (provisioning, configuration, etc)
of various devices. This includes notably end user routers, aka. *boxes*.
> GenieACS is a high performance Auto Configuration Server (ACS) for remote management
> of TR-069 enabled devices. It utilizes a declarative and fault tolerant
> configuration engine for automating complex provisioning scenarios at scale.
> It's battle-tested to handle hundreds of thousands and potentially millions
> of concurrent devices.
This tool provides:
* command line utility
* list devices
* ... *more to come* ...

View file

@ -1,13 +1,14 @@
[project] [project]
name = "genieacs-commander" name = "gacsco"
dynamic = ["version"] dynamic = ["version"]
description = "Remote control genieacs" description = "Remote control genieacs"
readme = "README.md" readme = "README.md"
requires-python = ">=3.13" requires-python = ">=3.13"
dependencies = [ dependencies = [
"httpx>=0.28.1", "httpx>=0.28.1",
"netaddr>=1.3.0",
"pydantic-settings[yaml]>=2.9.1", "pydantic-settings[yaml]>=2.9.1",
"typer>=0.15.3", "typer-slim>=0.15.4",
] ]
[dependency-groups] [dependency-groups]

View file

@ -1,16 +1,16 @@
from typing import Any, Type, Tuple from typing import Type, Tuple
from pathlib import Path from pathlib import Path
from pydantic import BaseModel from pydantic import BaseModel
from pydantic_settings import ( from pydantic_settings import (
BaseSettings, BaseSettings,
SecretsSettingsSource, # SecretsSettingsSource,
SettingsConfigDict, SettingsConfigDict,
PydanticBaseSettingsSource, PydanticBaseSettingsSource,
YamlConfigSettingsSource, YamlConfigSettingsSource,
) )
config_files = [Path(Path.cwd().root) / "etc" / "acsc" / "congig.yaml"] config_files = [Path(Path.cwd().root) / "etc" / "gacsco" / "congig.yaml"]
class Connection(BaseModel): class Connection(BaseModel):

View file

@ -3,7 +3,6 @@ from typing_extensions import Annotated
from json import dumps from json import dumps
import typer import typer
from rich import print as pprint
from gacsco.server import Server from gacsco.server import Server
from gacsco.utils import AsyncTyper, GacscoError from gacsco.utils import AsyncTyper, GacscoError
@ -19,11 +18,11 @@ async def main(name: Annotated[str | None, typer.Option(help="Your name")] = Non
... ...
# await server.login() # await server.login()
except GacscoError as err: except GacscoError as err:
pprint(err) typer.echo(err)
sys.exit(1) sys.exit(1)
if name is not None: if name is not None:
pprint(f"Hello {name} from genieacs-commander!") typer.echo(f"Hello {name} from genieacs-commander!")
@app.command(help="List devices") @app.command(help="List devices")
@ -32,7 +31,7 @@ async def list():
resp = await server.list_devices() resp = await server.list_devices()
print(dumps(resp)) print(dumps(resp))
except GacscoError as err: except GacscoError as err:
pprint(err) typer.echo(err)
@app.command(help="Reset the device") @app.command(help="Reset the device")
@ -40,12 +39,15 @@ def reset(): ...
@app.command(help="Get info on the device") @app.command(help="Get info on the device")
async def info(device_id: Annotated[str, typer.Argument()]): async def info(
device_id: Annotated[str, typer.Argument(help="device_id or WAN MAC address")],
):
"""Get device info, from its device_id or WAN interface MAC address"""
try: try:
resp = await server.get_device_info(device_id) resp = await server.get_device_info(device_id)
print(dumps(resp)) print(dumps(resp))
except GacscoError as err: except GacscoError as err:
pprint(err) typer.echo(err)
app() app()

View file

@ -1,11 +1,9 @@
from dataclasses import dataclass
from json import dumps from json import dumps
from urllib.parse import quote
import httpx import httpx
from netaddr import EUI, AddrFormatError
from gacsco.config import conf from gacsco.config import conf
from gacsco.utils import GacscoError
class LoginError(Exception): class LoginError(Exception):
@ -41,8 +39,15 @@ class Server:
return resp.json() return resp.json()
async def get_device_info(self, device_id: str): async def get_device_info(self, device_id: str):
async with httpx.AsyncClient(cookies=self.cookies) as client: try:
EUI(device_id)
except AddrFormatError:
query = {"_id": device_id} query = {"_id": device_id}
else:
query = {
"InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.MACAddress": device_id
}
async with httpx.AsyncClient(cookies=self.cookies) as client:
resp = await client.get( resp = await client.get(
f"{self.url}/devices/?", f"{self.url}/devices/?",
params={"query": dumps(query)}, params={"query": dumps(query)},

60
uv.lock generated
View file

@ -94,12 +94,13 @@ wheels = [
] ]
[[package]] [[package]]
name = "genieacs-commander" name = "gacsco"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "httpx" }, { name = "httpx" },
{ name = "netaddr" },
{ name = "pydantic-settings", extra = ["yaml"] }, { name = "pydantic-settings", extra = ["yaml"] },
{ name = "typer" }, { name = "typer-slim" },
] ]
[package.dev-dependencies] [package.dev-dependencies]
@ -111,8 +112,9 @@ dev = [
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "httpx", specifier = ">=0.28.1" }, { name = "httpx", specifier = ">=0.28.1" },
{ name = "netaddr", specifier = ">=1.3.0" },
{ name = "pydantic-settings", extras = ["yaml"], specifier = ">=2.9.1" }, { name = "pydantic-settings", extras = ["yaml"], specifier = ">=2.9.1" },
{ name = "typer", specifier = ">=0.15.3" }, { name = "typer-slim", specifier = ">=0.15.4" },
] ]
[package.metadata.requires-dev] [package.metadata.requires-dev]
@ -225,18 +227,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" },
] ]
[[package]]
name = "markdown-it-py"
version = "3.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mdurl" },
]
sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" },
]
[[package]] [[package]]
name = "matplotlib-inline" name = "matplotlib-inline"
version = "0.1.7" version = "0.1.7"
@ -250,12 +240,12 @@ wheels = [
] ]
[[package]] [[package]]
name = "mdurl" name = "netaddr"
version = "0.1.2" version = "1.3.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } sdist = { url = "https://files.pythonhosted.org/packages/54/90/188b2a69654f27b221fba92fda7217778208532c962509e959a9cee5229d/netaddr-1.3.0.tar.gz", hash = "sha256:5c3c3d9895b551b763779ba7db7a03487dc1f8e3b385af819af341ae9ef6e48a", size = 2260504, upload-time = "2024-05-28T21:30:37.743Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, { url = "https://files.pythonhosted.org/packages/12/cc/f4fe2c7ce68b92cbf5b2d379ca366e1edae38cccaad00f69f529b460c3ef/netaddr-1.3.0-py3-none-any.whl", hash = "sha256:c2c6a8ebe5554ce33b7d5b3a306b71bbb373e000bbbf2350dd5213cc56e3dbbe", size = 2262023, upload-time = "2024-05-28T21:30:34.191Z" },
] ]
[[package]] [[package]]
@ -415,28 +405,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" },
] ]
[[package]]
name = "rich"
version = "14.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markdown-it-py" },
{ name = "pygments" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" },
]
[[package]]
name = "shellingham"
version = "1.5.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
]
[[package]] [[package]]
name = "sniffio" name = "sniffio"
version = "1.3.1" version = "1.3.1"
@ -470,18 +438,16 @@ wheels = [
] ]
[[package]] [[package]]
name = "typer" name = "typer-slim"
version = "0.15.3" version = "0.15.4"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "click" }, { name = "click" },
{ name = "rich" },
{ name = "shellingham" },
{ name = "typing-extensions" }, { name = "typing-extensions" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/98/1a/5f36851f439884bcfe8539f6a20ff7516e7b60f319bbaf69a90dc35cc2eb/typer-0.15.3.tar.gz", hash = "sha256:818873625d0569653438316567861899f7e9972f2e6e0c16dab608345ced713c", size = 101641, upload-time = "2025-04-28T21:40:59.204Z" } sdist = { url = "https://files.pythonhosted.org/packages/a0/75/fb85e49851d127316f30b7f6001fa0d378c14afdac280dace89d49992518/typer_slim-0.15.4.tar.gz", hash = "sha256:1d9fe638da58f4bdeae891512c47ed83915f81e7a0ee062cf6c572ff38473128", size = 101615, upload-time = "2025-05-14T16:35:00.566Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/48/20/9d953de6f4367163d23ec823200eb3ecb0050a2609691e512c8b95827a9b/typer-0.15.3-py3-none-any.whl", hash = "sha256:c86a65ad77ca531f03de08d1b9cb67cd09ad02ddddf4b34745b5008f43b239bd", size = 45253, upload-time = "2025-04-28T21:40:56.269Z" }, { url = "https://files.pythonhosted.org/packages/b6/7f/44304801838529b1051e00fb30e1750cacd10da31da1f3b0fa0e96ae6045/typer_slim-0.15.4-py3-none-any.whl", hash = "sha256:6d134e1b048da37ceacc1ccc3ad28a6f966c8f0833cc1513cf12a21de0da8ed8", size = 45315, upload-time = "2025-05-14T16:34:59.053Z" },
] ]
[[package]] [[package]]