1
0
mirror of https://github.com/craigerl/aprsd.git synced 2026-08-16 00:23:34 -04:00

Compare commits

...

14 Commits

Author SHA1 Message Date
hemna b2f95b0f4e Updated Changelog for 3.1.2 2023-08-15 15:25:01 -04:00
hemna ae9e4d31ad Added support for ThirdParty packet types
The kiss clients now detect if the incomming packet is a third party
packet and then sends up the subpacket instead of the encapsulated
packet up to the consumer.
2023-08-15 14:24:03 -04:00
hemna 65a5a90458 Disable the Send GPS Beacon button
This patch disables the 'Send GPS Beacon' button for the webchat
command if the browser isn't connected over https
2023-08-14 18:45:13 -04:00
hemna 182887c20a Removed adhoc ssl support in webchat
This immediately breaks the beacon button.
This patch removes the dep for pyopenssl and cryptography
so that aprsd install on the rpi.

Unfortunately in order for the web page to get the Lat/Lon, the
browser must be connected over SSL.  Will have to create a workaround
for this later.
2023-08-14 18:34:25 -04:00
hemna f228144f4b Updated Changelog for v3.1.1 2023-08-07 13:01:52 -04:00
hemna db9e1d23d1 Fixed pep8 failures 2023-08-07 11:07:01 -04:00
hemna 986df391b2 re-enable USWeatherPlugin to use mapClick
The old MApClick.php api seems to work...re-enabling
2023-07-31 21:53:02 -04:00
hemna 3994235380 Merge pull request #128 from craigerl/fix_kiss
Fix sending packets over KISS interface
2023-07-28 18:08:54 -04:00
hemna 9ebf2f9a30 Fix sending packets over KISS interface
The KISS client sends the path as part of the headers, so we had
to strip out the path from the payload of each message so the path
wouldn't get listed twice.
2023-07-28 17:25:06 -04:00
hemna 011cfc55e1 Use config web_ip for running admin ui from module
When running the web admin interface with
'python -m aprsd.wsgi' the Flask app global now uses
the web_ip config entry for listening.  Also disabled
debug output.
2023-07-26 08:47:22 -04:00
hemna e0c3c5cbbf remove loop log 2023-07-25 20:45:55 -04:00
hemna 26f354b3a9 Max out the client reconnect backoff to 5
This patch adjusts the backoff mechanism for aprs client
reconnect to a max backoff sleep of 5 seconds.   This prevents
an exponential backoff when connection retrying.
2023-07-24 17:03:29 -04:00
hemna 922a6dbb35 Merge pull request #125 from craigerl/update-Dockerfile
Update the Dockerfile
2023-07-24 14:34:06 -04:00
hemna d03c4fc096 Update the Dockerfile
This updates the main Dockerfile to be the same as the
Dockerfile-dev other than using the official pypi package for
aprsd.
2023-07-24 11:36:28 -04:00
15 changed files with 249 additions and 156 deletions
+20
View File
@@ -1,9 +1,29 @@
CHANGES
=======
v3.1.2
------
* Added support for ThirdParty packet types
* Disable the Send GPS Beacon button
* Removed adhoc ssl support in webchat
v3.1.1
------
* Updated Changelog for v3.1.1
* Fixed pep8 failures
* re-enable USWeatherPlugin to use mapClick
* Fix sending packets over KISS interface
* Use config web\_ip for running admin ui from module
* remove loop log
* Max out the client reconnect backoff to 5
* Update the Dockerfile
v3.1.0
------
* Changelog updates for v3.1.0
* Use CONF.admin.web\_port for single launch web admin
* Fixed sio namespace registration
* Update Dockerfile-dev to include uwsgi
+12 -2
View File
@@ -49,8 +49,10 @@ class Client:
@property
def client(self):
if not self._client:
LOG.info("Creating APRS client")
self._client = self.setup_connection()
if self.filter:
LOG.info("Creating APRS client filter")
self._client.set_filter(self.filter)
return self._client
@@ -159,7 +161,11 @@ class APRSISClient(Client):
LOG.error(f"Unable to connect to APRS-IS server. '{e}' ")
connected = False
time.sleep(backoff)
backoff = backoff * 2
# Don't allow the backoff to go to inifinity.
if backoff > 5:
backoff = 5
else:
backoff += 1
continue
LOG.debug(f"Logging in to APRS-IS with user '{user}'")
self._client = aprs_client
@@ -229,7 +235,11 @@ class KISSClient(Client):
# LOG.debug(f"Decoding {msg}")
raw = aprslib.parse(str(frame))
return core.Packet.factory(raw)
packet = core.Packet.factory(raw)
if isinstance(packet, core.ThirdParty):
return packet.subpacket
else:
return packet
@trace.trace
def setup_connection(self):
+9 -15
View File
@@ -81,27 +81,18 @@ class KISS3Client:
LOG.debug("Start blocking KISS consumer")
self._parse_callback = callback
self.kiss.read(callback=self.parse_frame, min_frames=None)
LOG.debug("END blocking KISS consumer")
LOG.debug(f"END blocking KISS consumer {self.kiss}")
def send(self, packet):
"""Send an APRS Message object."""
# payload = (':%-9s:%s' % (
# msg.tocall,
# payload
# )).encode('US-ASCII'),
# payload = str(msg).encode('US-ASCII')
payload = None
path = ["WIDE1-1", "WIDE2-1"]
if isinstance(packet, core.AckPacket):
msg_payload = f"ack{packet.msgNo}"
elif isinstance(packet, core.Packet):
payload = packet.raw.encode("US-ASCII")
path = ["WIDE2-1"]
if isinstance(packet, core.Packet):
packet.prepare()
payload = packet.payload.encode("US-ASCII")
else:
msg_payload = f"{packet.raw}{{{str(packet.msgNo)}"
if not payload:
payload = (
":{:<9}:{}".format(
packet.to_call,
@@ -109,9 +100,12 @@ class KISS3Client:
)
).encode("US-ASCII")
LOG.debug(f"Send '{payload}' TO KISS")
LOG.debug(
f"KISS Send '{payload}' TO '{packet.to_call}' From "
f"'{packet.from_call}' with PATH '{path}'",
)
frame = Frame.ui(
destination=packet.to_call,
destination="APZ100",
source=packet.from_call,
path=path,
info=payload,
+9 -3
View File
@@ -265,8 +265,12 @@ def _stats():
time_format = "%m-%d-%Y %H:%M:%S"
stats_dict = stats_obj.stats()
# Webchat doesnt need these
del stats_dict["aprsd"]["watch_list"]
del stats_dict["aprsd"]["seen_list"]
if "watch_list" in stats_dict["aprsd"]:
del stats_dict["aprsd"]["watch_list"]
if "seen_list" in stats_dict["aprsd"]:
del stats_dict["aprsd"]["seen_list"]
if "threads" in stats_dict["aprsd"]:
del stats_dict["aprsd"]["threads"]
# del stats_dict["email"]
# del stats_dict["plugins"]
# del stats_dict["messages"]
@@ -481,7 +485,9 @@ def webchat(ctx, flush, port):
LOG.info("Start socketio.run()")
socketio.run(
flask_app,
ssl_context="adhoc",
# This is broken for now after removing cryptography
# and pyopenssl
# ssl_context="adhoc",
host=CONF.admin.web_ip,
port=port,
)
+95 -39
View File
@@ -25,6 +25,7 @@ PACKET_TYPE_OBJECT = "object"
PACKET_TYPE_UNKNOWN = "unknown"
PACKET_TYPE_STATUS = "status"
PACKET_TYPE_BEACON = "beacon"
PACKET_TYPE_THIRDPARTY = "thirdparty"
PACKET_TYPE_UNCOMPRESSED = "uncompressed"
@@ -57,6 +58,8 @@ class Packet(metaclass=abc.ABCMeta):
# or holds the raw string from input packet
raw: str = None
raw_dict: dict = field(repr=False, default_factory=lambda: {})
# Built by calling prepare(). raw needs this built first.
payload: str = None
# Fields related to sending packets out
send_count: int = field(repr=False, default=0)
@@ -92,14 +95,28 @@ class Packet(metaclass=abc.ABCMeta):
def prepare(self):
"""Do stuff here that is needed prior to sending over the air."""
# now build the raw message for sending
self._build_payload()
self._build_raw()
def _build_payload(self):
"""The payload is the non headers portion of the packet."""
msg = self._filter_for_send().rstrip("\n")
self.payload = (
f":{self.to_call.ljust(9)}"
f":{msg}"
)
def _build_raw(self):
"""Build the self.raw string which is what is sent over the air."""
self.raw = self._filter_for_send().rstrip("\n")
"""Build the self.raw which is what is sent over the air."""
self.raw = "{}>APZ100:{}".format(
self.from_call,
self.payload,
)
LOG.debug(f"_build_raw: payload '{self.payload}' raw '{self.raw}'")
@staticmethod
def factory(raw_packet):
"""Factory method to create a packet from a raw packet string."""
raw = raw_packet
raw["raw_dict"] = raw.copy()
translate_fields = {
@@ -118,6 +135,19 @@ class Packet(metaclass=abc.ABCMeta):
packet_type = get_packet_type(raw)
raw["packet_type"] = packet_type
class_name = TYPE_LOOKUP[packet_type]
if packet_type == PACKET_TYPE_THIRDPARTY:
# We have an encapsulated packet!
# So we need to decode it and return the inner packet
# as the packet we are going to process.
# This is a recursive call to the factory
subpacket_raw = raw["subpacket"]
subpacket = Packet.factory(subpacket_raw)
del raw["subpacket"]
# raw["subpacket"] = subpacket
packet = dacite.from_dict(data_class=class_name, data=raw)
packet.subpacket = subpacket
return packet
if packet_type == PACKET_TYPE_UNKNOWN:
# Try and figure it out here
if "latitude" in raw:
@@ -233,7 +263,7 @@ class PathPacket(Packet):
path: List[str] = field(default_factory=list)
via: str = None
def _build_raw(self):
def _build_payload(self):
raise NotImplementedError
@@ -245,13 +275,8 @@ class AckPacket(PathPacket):
if self.response:
LOG.warning("Response set!")
def _build_raw(self):
"""Build the self.raw which is what is sent over the air."""
self.raw = "{}>APZ100::{}:ack{}".format(
self.from_call,
self.to_call.ljust(9),
self.msgNo,
)
def _build_payload(self):
self.payload = f":{self.to_call.ljust(9)}:ack{self.msgNo}"
@dataclass
@@ -262,13 +287,8 @@ class RejectPacket(PathPacket):
if self.response:
LOG.warning("Response set!")
def _build_raw(self):
"""Build the self.raw which is what is sent over the air."""
self.raw = "{}>APZ100::{} :rej{}".format(
self.from_call,
self.to_call.ljust(9),
self.msgNo,
)
def _build_payload(self):
self.payload = f":{self.to_call.ljust(9)} :rej{self.msgNo}"
@dataclass
@@ -285,10 +305,8 @@ class MessagePacket(PathPacket):
# We all miss George Carlin
return re.sub("fuck|shit|cunt|piss|cock|bitch", "****", message)
def _build_raw(self):
"""Build the self.raw which is what is sent over the air."""
self.raw = "{}>APZ100::{}:{}{{{}".format(
self.from_call,
def _build_payload(self):
self.payload = ":{}:{}{{{}".format(
self.to_call.ljust(9),
self._filter_for_send().rstrip("\n"),
str(self.msgNo),
@@ -301,7 +319,7 @@ class StatusPacket(PathPacket):
messagecapable: bool = False
comment: str = None
def _build_raw(self):
def _build_payload(self):
raise NotImplementedError
@@ -400,16 +418,24 @@ class GPSPacket(PathPacket):
time_zulu = result_utc_datetime.strftime("%d%H%M")
return time_zulu
def _build_raw(self):
def _build_payload(self):
"""The payload is the non headers portion of the packet."""
time_zulu = self._build_time_zulu()
lat = self.latitude
long = self.longitude
self.payload = (
f"@{time_zulu}z{lat}{self.symbol_table}"
f"{long}{self.symbol}"
)
if self.comment:
self.payload = f"{self.payload}{self.comment}"
def _build_raw(self):
self.raw = (
f"{self.from_call}>{self.to_call},WIDE2-1:"
f"@{time_zulu}z{self.latitude}{self.symbol_table}"
f"{self.longitude}{self.symbol}"
f"{self.payload}"
)
if self.comment:
self.raw = f"{self.raw}{self.comment}"
@dataclass
@@ -422,7 +448,7 @@ class MicEPacket(GPSPacket):
# 0 to 360
course: int = 0
def _build_raw(self):
def _build_payload(self):
raise NotImplementedError
@@ -436,6 +462,19 @@ class ObjectPacket(GPSPacket):
# 0 to 360
course: int = 0
def _build_payload(self):
time_zulu = self._build_time_zulu()
lat = self.convert_latitude(self.latitude)
long = self.convert_longitude(self.longitude)
self.payload = (
f"*{time_zulu}z{lat}{self.symbol_table}"
f"{long}{self.symbol}"
)
if self.comment:
self.payload = f"{self.payload}{self.comment}"
def _build_raw(self):
"""
REPEAT builds packets like
@@ -446,17 +485,11 @@ class ObjectPacket(GPSPacket):
callsign is the station callsign for the object
The frequency, uplink_tone, offset is part of the comment
"""
time_zulu = self._build_time_zulu()
lat = self.convert_latitude(self.latitude)
long = self.convert_longitude(self.longitude)
self.raw = (
f"{self.from_call}>APZ100:;{self.to_call:9s}"
f"*{time_zulu}z{lat}{self.symbol_table}"
f"{long}{self.symbol}"
f"{self.payload}"
)
if self.comment:
self.raw = f"{self.raw}{self.comment}"
@dataclass()
@@ -474,7 +507,7 @@ class WeatherPacket(GPSPacket):
pressure: float = 0.00
comment: str = None
def _build_raw(self):
def _build_payload(self):
"""Build an uncompressed weather packet
Format =
@@ -502,7 +535,6 @@ class WeatherPacket(GPSPacket):
time_zulu = self._build_time_zulu()
contents = [
f"{self.from_call}>{self.to_call},WIDE1-1,WIDE2-1:",
f"@{time_zulu}z{self.latitude}{self.symbol_table}",
f"{self.longitude}{self.symbol}",
f"{self.wind_direction:03d}",
@@ -523,11 +555,32 @@ class WeatherPacket(GPSPacket):
# Barometric pressure (in tenths of millibars/tenths of hPascal)
f"b{self.pressure:05.0f}",
]
if self.comment:
contents.append(self.comment)
self.payload = "".join(contents)
self.raw = "".join(contents)
def _build_raw(self):
self.raw = (
f"{self.from_call}>{self.to_call},WIDE1-1,WIDE2-1:"
f"{self.payload}"
)
class ThirdParty(Packet):
# Holds the encapsulated packet
subpacket: Packet = None
def __repr__(self):
"""Build the repr version of the packet."""
repr_str = (
f"{self.__class__.__name__}:"
f" From: {self.from_call} "
f" To: {self.to_call} "
f" Subpacket: {repr(self.subpacket)}"
)
return repr_str
TYPE_LOOKUP = {
@@ -540,6 +593,7 @@ TYPE_LOOKUP = {
PACKET_TYPE_STATUS: StatusPacket,
PACKET_TYPE_BEACON: GPSPacket,
PACKET_TYPE_UNKNOWN: Packet,
PACKET_TYPE_THIRDPARTY: ThirdParty,
}
@@ -566,6 +620,8 @@ def get_packet_type(packet: dict):
elif pkt_format == PACKET_TYPE_UNCOMPRESSED:
if packet.get("symbol", None) == "_":
packet_type = PACKET_TYPE_WX
elif pkt_format == PACKET_TYPE_THIRDPARTY:
packet_type = PACKET_TYPE_THIRDPARTY
return packet_type
+3 -3
View File
@@ -33,9 +33,9 @@ def get_weather_gov_for_gps(lat, lon):
)
try:
url2 = (
# "https://forecast.weather.gov/MapClick.php?lat=%s"
# "&lon=%s&FcstType=json" % (lat, lon)
f"https://api.weather.gov/points/{lat},{lon}"
"https://forecast.weather.gov/MapClick.php?lat=%s"
"&lon=%s&FcstType=json" % (lat, lon)
# f"https://api.weather.gov/points/{lat},{lon}"
)
LOG.debug(f"Fetching weather '{url2}'")
response = requests.get(url2, headers=headers)
+8 -1
View File
@@ -37,11 +37,18 @@ class USWeatherPlugin(plugin.APRSDRegexCommandPluginBase, plugin.APRSFIKEYMixin)
def process(self, packet):
LOG.info("Weather Plugin")
fromcall = packet.from_call
message = packet.get("message_text", None)
# message = packet.get("message_text", None)
# ack = packet.get("msgNo", "0")
a = re.search(r"^.*\s+(.*)", message)
if a is not None:
searchcall = a.group(1)
searchcall = searchcall.upper()
else:
searchcall = fromcall
api_key = CONF.aprs_fi.apiKey
try:
aprs_data = plugin_utils.get_aprs_fi(api_key, fromcall)
aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall)
except Exception as ex:
LOG.error(f"Failed to fetch aprs.fi data {ex}")
return "Failed to fetch aprs.fi location"
+4 -1
View File
@@ -18,7 +18,10 @@ LOG = logging.getLogger("APRSD")
def magic_word_authenticator(sock):
magic = sock.recv(len(CONF.rpc_settings.magic_word)).decode()
if magic != CONF.rpc_settings.magic_word:
raise AuthenticationError(f"wrong magic word {magic}")
raise AuthenticationError(
f"wrong magic word passed in '{magic}'"
f" != '{CONF.rpc_settings.magic_word}'",
)
return sock, None
-1
View File
@@ -246,7 +246,6 @@ class APRSDStats:
},
"plugins": plugin_stats,
}
LOG.debug(f"STATS = {stats}")
LOG.info("APRSD Stats: DONE")
return stats
+5
View File
@@ -26,6 +26,11 @@
start_update();
init_chat();
reset_Tabs();
if (location.protocol != 'https:') {
// Have to disable the beacon button.
$('#send_beacon').prop('disabled', true);
}
});
</script>
</head>
+6 -1
View File
@@ -330,7 +330,12 @@ if __name__ == "__main__":
setup_logging(app, log_level)
sio.register_namespace(LoggingNamespace("/logs"))
CONF.log_opt_values(LOG, logging.DEBUG)
app.run(threaded=True, debug=True, port=CONF.admin.web_port)
app.run(
threaded=True,
debug=False,
port=CONF.admin.web_port,
host=CONF.admin.web_ip,
)
if __name__ == "uwsgi_file_aprsd_wsgi":
+27 -27
View File
@@ -4,7 +4,7 @@
#
# pip-compile --annotation-style=line dev-requirements.in
#
add-trailing-comma==3.0.0 # via gray
add-trailing-comma==3.0.1 # via gray
alabaster==0.7.13 # via sphinx
attrs==23.1.0 # via jsonschema, referencing
autoflake==1.5.3 # via gray
@@ -13,75 +13,75 @@ black==23.7.0 # via gray
build==0.10.0 # via pip-tools
cachetools==5.3.1 # via tox
certifi==2023.7.22 # via requests
cfgv==3.3.1 # via pre-commit
chardet==5.1.0 # via tox
cfgv==3.4.0 # via pre-commit
chardet==5.2.0 # via tox
charset-normalizer==3.2.0 # via requests
click==8.1.6 # via black, pip-tools
colorama==0.4.6 # via tox
commonmark==0.9.1 # via rich
configargparse==1.7 # via gray
coverage[toml]==7.2.7 # via pytest-cov
coverage[toml]==7.3.0 # via pytest-cov
distlib==0.3.7 # via virtualenv
docutils==0.20.1 # via sphinx
exceptiongroup==1.1.2 # via pytest
exceptiongroup==1.1.3 # via pytest
filelock==3.12.2 # via tox, virtualenv
fixit==0.1.4 # via gray
flake8==6.0.0 # via -r dev-requirements.in, fixit, pep8-naming
flake8==6.1.0 # via -r dev-requirements.in, fixit, pep8-naming
gray==0.13.0 # via -r dev-requirements.in
identify==2.5.26 # via pre-commit
idna==3.4 # via requests
imagesize==1.4.1 # via sphinx
importlib-resources==6.0.0 # via fixit
importlib-resources==6.0.1 # via fixit
iniconfig==2.0.0 # via pytest
isort==5.12.0 # via -r dev-requirements.in, gray
jinja2==3.1.2 # via sphinx
jsonschema==4.18.4 # via fixit
jsonschema==4.19.0 # via fixit
jsonschema-specifications==2023.7.1 # via jsonschema
libcst==1.0.1 # via fixit
markupsafe==2.1.3 # via jinja2
mccabe==0.7.0 # via flake8
mypy==1.4.1 # via -r dev-requirements.in
mypy==1.5.0 # via -r dev-requirements.in
mypy-extensions==1.0.0 # via black, mypy, typing-inspect
nodeenv==1.8.0 # via pre-commit
packaging==23.1 # via black, build, pyproject-api, pytest, sphinx, tox
pathspec==0.11.1 # via black
pathspec==0.11.2 # via black
pep8-naming==0.13.3 # via -r dev-requirements.in
pip-tools==7.1.0 # via -r dev-requirements.in
platformdirs==3.9.1 # via black, tox, virtualenv
pip-tools==7.3.0 # via -r dev-requirements.in
platformdirs==3.10.0 # via black, tox, virtualenv
pluggy==1.2.0 # via pytest, tox
pre-commit==3.3.3 # via -r dev-requirements.in
pycodestyle==2.10.0 # via flake8
pyflakes==3.0.1 # via autoflake, flake8
pygments==2.15.1 # via rich, sphinx
pycodestyle==2.11.0 # via flake8
pyflakes==3.1.0 # via autoflake, flake8
pygments==2.16.1 # via rich, sphinx
pyproject-api==1.5.3 # via tox
pyproject-hooks==1.0.0 # via build
pytest==7.4.0 # via -r dev-requirements.in, pytest-cov
pytest-cov==4.1.0 # via -r dev-requirements.in
pyupgrade==3.9.0 # via gray
pyupgrade==3.10.1 # via gray
pyyaml==6.0.1 # via fixit, libcst, pre-commit
referencing==0.30.0 # via jsonschema, jsonschema-specifications
referencing==0.30.2 # via jsonschema, jsonschema-specifications
requests==2.31.0 # via sphinx
rich==12.6.0 # via gray
rpds-py==0.9.2 # via jsonschema, referencing
snowballstemmer==2.2.0 # via sphinx
sphinx==7.0.1 # via -r dev-requirements.in
sphinxcontrib-applehelp==1.0.4 # via sphinx
sphinxcontrib-devhelp==1.0.2 # via sphinx
sphinxcontrib-htmlhelp==2.0.1 # via sphinx
sphinx==7.1.2 # via -r dev-requirements.in, sphinxcontrib-applehelp, sphinxcontrib-devhelp, sphinxcontrib-htmlhelp, sphinxcontrib-qthelp, sphinxcontrib-serializinghtml
sphinxcontrib-applehelp==1.0.7 # via sphinx
sphinxcontrib-devhelp==1.0.5 # via sphinx
sphinxcontrib-htmlhelp==2.0.4 # via sphinx
sphinxcontrib-jsmath==1.0.1 # via sphinx
sphinxcontrib-qthelp==1.0.3 # via sphinx
sphinxcontrib-serializinghtml==1.1.5 # via sphinx
tokenize-rt==5.1.0 # via add-trailing-comma, pyupgrade
sphinxcontrib-qthelp==1.0.6 # via sphinx
sphinxcontrib-serializinghtml==1.1.8 # via sphinx
tokenize-rt==5.2.0 # via add-trailing-comma, pyupgrade
toml==0.10.2 # via autoflake
tomli==2.0.1 # via black, build, coverage, mypy, pip-tools, pyproject-api, pyproject-hooks, pytest, tox
tox==4.6.4 # via -r dev-requirements.in
tox==4.8.0 # via -r dev-requirements.in
typing-extensions==4.7.1 # via libcst, mypy, typing-inspect
typing-inspect==0.9.0 # via libcst
unify==0.5 # via gray
untokenize==0.1.1 # via unify
urllib3==2.0.4 # via requests
virtualenv==20.24.1 # via pre-commit, tox
wheel==0.41.0 # via pip-tools
virtualenv==20.24.3 # via pre-commit, tox
wheel==0.41.1 # via pip-tools
# The following packages are considered to be unsafe in a requirements file:
# pip
+44 -45
View File
@@ -1,62 +1,61 @@
#FROM python:3-bullseye as aprsd
FROM ubuntu:22.04 as aprsd
FROM python:3.11-slim as build
# Dockerfile for building a container during aprsd development.
ARG UID
ARG GID
ARG TZ
ARG VERSION=3.0.3
ARG BUILDX_QEMU_ENV
ENV APRS_USER=aprs
ENV HOME=/home/aprs
ARG VERSION=3.1.0
ENV TZ=${TZ:-US/Eastern}
ENV UID=${UID:-1000}
ENV GID=${GID:-1000}
ENV LC_ALL=C.UTF-8
ENV LANG=C.UTF-8
ENV APRSD_PIP_VERSION=${VERSION}
ENV DEBIAN_FRONTEND=noninteractive
RUN apt update
RUN apt install -y git build-essential
RUN apt install -y libffi-dev python3-dev libssl-dev libxml2-dev libxslt-dev
RUN apt install -y python3 python3-pip python3-dev python3-lxml python3-setuptools-rust
RUN apt install -y libffi-dev cargo pkg-config
RUN pip3 install -U pip
RUN pip3 install -U setuptools_rust
ENV PIP_DEFAULT_TIMEOUT=100 \
# Allow statements and log messages to immediately appear
PYTHONUNBUFFERED=1 \
# disable a pip version check to reduce run-time & log-spam
PIP_DISABLE_PIP_VERSION_CHECK=1 \
# cache is useless in docker image, so disable to reduce image size
PIP_NO_CACHE_DIR=1
RUN addgroup --gid $GID $APRS_USER
RUN useradd -m -u $UID -g $APRS_USER $APRS_USER
RUN set -ex \
# Create a non-root user
&& addgroup --system --gid 1001 appgroup \
&& useradd --uid 1001 --gid 1001 -s /usr/bin/bash -m -d /app appuser \
# Upgrade the package index and install security upgrades
&& apt-get update \
&& apt-get upgrade -y \
&& apt-get install -y git build-essential curl vim libffi-dev \
python3-dev libssl-dev libxml2-dev libxslt-dev telnet sudo \
# Install dependencies
# Clean up
&& apt-get autoremove -y \
&& apt-get clean -y
# Handle an extremely specific issue when building the cryptography package for
# 32-bit architectures within QEMU running on a 64-bit host (issue #30).
RUN if [ "${BUILDX_QEMU_ENV}" = "true" -a "$(getconf LONG_BIT)" = "32" ]; then \
pip3 install -U cryptography==3.3.2; \
else \
pip3 install cryptography ;\
fi
# Ensure /config is there with a default config file
USER root
# Install aprsd
### Final stage
FROM build as final
WORKDIR /app
RUN pip3 install aprsd==$APRSD_PIP_VERSION
RUN mkdir -p /config
RUN pip install gevent uwsgi
RUN which aprsd
RUN mkdir /config
RUN chown -R appuser:appgroup /app
RUN chown -R appuser:appgroup /config
USER appuser
RUN which aprsd
RUN aprsd sample-config > /config/aprsd.conf
RUN chown -R $APRS_USER:$APRS_USER /config
RUN chown -R $APRS_USER:$APRS_USER $HOME
# override this to run another configuration
ENV CONF default
VOLUME ["/config", "/plugins"]
ADD bin/run.sh /app
ADD bin/listen.sh /app
ADD bin/admin.sh /app
USER $APRS_USER
ADD bin/run.sh /usr/local/bin
ADD bin/listen.sh /usr/local/bin
ENTRYPOINT ["/usr/local/bin/run.sh"]
# For the web admin interface
EXPOSE 8001
ENTRYPOINT ["/app/run.sh"]
VOLUME ["/config"]
# Set the user to run the application
USER appuser
HEALTHCHECK --interval=5m --timeout=12s --start-period=30s \
CMD aprsd healthcheck --config /config/aprsd.conf
-2
View File
@@ -29,14 +29,12 @@ kiss3
attrs
# for mobile checking
user-agents
pyopenssl
dataclasses
dacite2
oslo.config
rpyc
# Pin this here so it doesn't require a compile on
# raspi
cryptography
shellingham
geopy
rush
+7 -16
View File
@@ -4,39 +4,33 @@
#
# pip-compile --annotation-style=line requirements.in
#
anyio==3.7.1 # via httpcore
aprslib==0.7.2 # via -r requirements.in
attrs==23.1.0 # via -r requirements.in, ax253, kiss3, rush
ax253==0.1.5.post1 # via kiss3
beautifulsoup4==4.12.2 # via -r requirements.in
bidict==0.22.1 # via python-socketio
bitarray==2.8.0 # via ax253, kiss3
bitarray==2.8.1 # via ax253, kiss3
blinker==1.6.2 # via flask
certifi==2023.7.22 # via httpcore, requests
cffi==1.15.1 # via cryptography
certifi==2023.7.22 # via requests
charset-normalizer==3.2.0 # via requests
click==8.1.6 # via -r requirements.in, click-completion, click-params, flask
click-completion==0.5.2 # via -r requirements.in
click-params==0.4.1 # via -r requirements.in
commonmark==0.9.1 # via rich
cryptography==41.0.2 # via -r requirements.in, pyopenssl
dacite2==2.0.0 # via -r requirements.in
dataclasses==0.6 # via -r requirements.in
debtcollector==2.5.0 # via oslo-config
decorator==5.1.1 # via validators
dnspython==2.4.0 # via eventlet
dnspython==2.4.2 # via eventlet
eventlet==0.33.3 # via -r requirements.in
exceptiongroup==1.1.2 # via anyio
flask==2.3.2 # via -r requirements.in, flask-httpauth, flask-socketio
flask-httpauth==4.8.0 # via -r requirements.in
flask-socketio==5.3.4 # via -r requirements.in
flask-socketio==5.3.5 # via -r requirements.in
geographiclib==2.0 # via geopy
geopy==2.3.0 # via -r requirements.in
gevent==23.7.0 # via -r requirements.in
greenlet==2.0.2 # via eventlet, gevent
h11==0.14.0 # via httpcore
httpcore==0.17.3 # via dnspython
idna==3.4 # via anyio, requests
idna==3.4 # via requests
imapclient==2.3.1 # via -r requirements.in
importlib-metadata==6.8.0 # via ax253, kiss3
itsdangerous==2.1.2 # via flask
@@ -49,9 +43,7 @@ oslo-i18n==6.0.0 # via oslo-config
pbr==5.11.1 # via -r requirements.in, oslo-i18n, stevedore
pluggy==1.2.0 # via -r requirements.in
plumbum==1.8.2 # via rpyc
pycparser==2.21 # via cffi
pygments==2.15.1 # via rich
pyopenssl==23.2.0 # via -r requirements.in
pygments==2.16.1 # via rich
pyserial==3.5 # via pyserial-asyncio
pyserial-asyncio==0.6 # via kiss3
python-engineio==4.5.1 # via python-socketio
@@ -65,7 +57,6 @@ rpyc==5.3.1 # via -r requirements.in
rush==2021.4.0 # via -r requirements.in
shellingham==1.5.0.post1 # via -r requirements.in, click-completion
six==1.16.0 # via -r requirements.in, click-completion, eventlet, imapclient
sniffio==1.3.0 # via anyio, dnspython, httpcore
soupsieve==2.4.1 # via beautifulsoup4
stevedore==5.1.0 # via oslo-config
tabulate==0.9.0 # via -r requirements.in
@@ -75,7 +66,7 @@ update-checker==0.18.0 # via -r requirements.in
urllib3==2.0.4 # via requests
user-agents==2.2.0 # via -r requirements.in
validators==0.20.0 # via click-params
werkzeug==2.3.6 # via -r requirements.in, flask
werkzeug==2.3.7 # via -r requirements.in, flask
wrapt==1.15.0 # via -r requirements.in, debtcollector
zipp==3.16.2 # via importlib-metadata
zope-event==5.0 # via gevent