commit 216e216dee0985cbeb74e7d6c08a66f0dd5ec174 Author: YuruC3 Date: Wed Feb 25 23:16:46 2026 +0100 Big init diff --git a/HBX2LV4aMAAstst.png b/HBX2LV4aMAAstst.png new file mode 100644 index 0000000..bc5e4d4 Binary files /dev/null and b/HBX2LV4aMAAstst.png differ diff --git a/README b/README new file mode 100644 index 0000000..64d4197 --- /dev/null +++ b/README @@ -0,0 +1,5 @@ +Hi + +

+ +

diff --git a/cont/code/classes.py b/cont/code/classes.py new file mode 100644 index 0000000..4b7990f --- /dev/null +++ b/cont/code/classes.py @@ -0,0 +1,75 @@ +from pydantic import BaseModel +from sqlalchemy import Column, Date, Float, Integer, String, text +from datetime import datetime, timezone, timedelta +from typing import Optional, Annotated +from sqlalchemy.dialects.mysql import MEDIUMINT, TINYINT, TEXT, TIMESTAMP, FLOAT +from sqlalchemy.ext.declarative import declarative_base + +# ----------- + +AlchemyBase = declarative_base() + +# DATABASE "CLASSESS"-------------------------------------------- +def sensorsqldata(table_name: Annotated[str, "Name of the table"]): + + class SensorSQLDataClass(AlchemyBase): + __table_args__ = {'extend_existing': True} + + __tablename__ = table_name + + id = Column(MEDIUMINT(unsigned=True), primary_key=True, autoincrement=True) + time_stamp = Column( + TIMESTAMP, + server_default=text("CURRENT_TIMESTAMP"), + server_onupdate=text("CURRENT_TIMESTAMP"), + nullable=False, + ) + TEMP = Column(TINYINT) + TEMP = Column(TEXT) + TEMP = Column(Float) + TEMP = Column(Float) + TEMP = Column(Float, nullable=True) + TEMP = Column(Float, nullable=True) + TEMP = Column(Float, nullable=True) + + return SensorSQLDataClass + + +# Custom-------------------------------------------- +class snmpValueToHost(): + def __init__(self, + lastV: Annotated[str, "Last value\nNone when initializing"] = None, + remote: Annotated[str, "Needs to be a valid IP address"] = None, + nextV: Annotated[str, "Value to insert next\nNone when initializing"] = None, + lasttime: Annotated[float, "Last time since sending data\nSince Epoch"] = time.time(), + lastStatus: Annotated[bool, "True if value newest value was inserted\nFalse if data is pending to be sent"] = False + ): + self.__lastValue = lastV # Save last sent value + self.__remote = remote # Remote IP address of the SNMP client + self.__nextValue = nextV # Next value to send. Maybe will be used + self.__lastSentStatus = lastStatus # If previously sent new data, set to True. + self.__lastSentTime = lasttime # The time since the Epoch of the last sent data* + + + def ___str__(): + return self.__lastValue + + def __changeLastSentValue(slef): + self.__lastValue = self.__nextValue + return True + + def __updateLastSentTime(self): + self.__lastSentTime = time.time() + return True + + def __updateSentStatus(self): + if self.__lastSentStatus: + self.__lastSentStatus = False + return False + else: + self.__lastSentStatus = True + return True + + def updateStats(self, nextV: Annotated[int, "Value that will be set as the new one"]): + + ... \ No newline at end of file diff --git a/cont/code/funct.py b/cont/code/funct.py new file mode 100644 index 0000000..4215099 --- /dev/null +++ b/cont/code/funct.py @@ -0,0 +1,123 @@ +import influxdb_client, sqlalchemy, random, os +# from sqlmodel import Field, Session, SQLModel, create_engine, select +from sqlalchemy import create_engine, exc # , Column, Integer, String, Numeric +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker + +from typing import Annotated, Final +from classes import * + +from influxdb_client import InfluxDBClient, Point, WritePrecision +from influxdb_client.client.write_api import SYNCHRONOUS, ASYNCHRONOUS, WriteOptions +# import pymysql + +# FluxQL ENV---------------------------------------- +USEINFLUX: Final[bool] = os.getenv("USEINFLUX", True) +if USEINFLUX: + INFLXDBTOKEN: Final[str] = os.getenv("INFLXDBTOKEN", "123" ) + INFLUXBCKT: Final[str] = os.getenv("INFLUXBCKT", "SNMPyth") + INFLUXORG: Final[str] = os.getenv("INFLUXORG", "staging") + INFLXDBURL: Final[str] = os.getenv("INFLXDBURL", "http://localhost:8086") + INFXLUXDB_MEASUEREMENT: Final[str] = os.getenv("INFXLUXDB_MEASUEREMENT", "SNMPyth-containrr") + INFLX_SEPARATE_POINTS: Final[float] = float(os.getenv("INFLX_SEPARATE_POINTS", 0.1)) +# SQL ENV------------------------------------------- +USESQL: Final[bool] = os.getenv("USESQL", False) +if USESQL: + DBENGINE: Final[str] = os.getenv("DBENGINE", "mysql+pymysql") + DBADDR: Final[str] = os.getenv("DBADDR", "127.0.0.1") + DBUSR: Final[str] = os.getenv("DBUSR", "root") + DBPWD: Final[str] = os.getenv("DBPWD", "6767") + DBNAME: Final[str] = os.getenv("DBNAME", "TEMP_SENSR") + + +# DBprepare------------------------------------------- +# INFLUX +if USEINFLUX: + fluxdb_client = influxdb_client.InfluxDBClient(url=INFLXDBURL, token=INFLXDBTOKEN, org=INFLUXORG) + write_fluxdb_api = fluxdb_client.write_api(write_options=SYNCHRONOUS) + query_fluxdb_api = fluxdb_client.query_api() +# SQL +if USESQL: + engine = create_engine( + f"{DBENGINE}://{DBUSR}:{DBPWD}@{DBADDR}/{DBNAME}", + pool_pre_ping=True, # Check connection liveness before using and if needed, recconect + pool_recycle=3600 # recycle connections older than N (3600 in this case) seconds + ) + Session = sessionmaker(bind=engine) + + + +def tempsnsrIntoSQLDB( + tableInp: Annotated[int, "Name of the table"], + insertData: Annotated[str, "Data to insert"] + ) -> int: + f""" + Insert {DBNAME} data into MariaDB + :tableInp: Name of the table + :return: 0 if succesfull, 1 if rolled back on error, 2 if SQL is not enabled 3 if no such table + """ + + if not USESQL: + return 2 + + # Check if table exists + if not sqlalchemy.inspect(engine).has_table(tableInp): + return 3 + + with Session() as session: + + try: + + data = sensorsqldata(tableInp) + # print("2") + + if inpayload: + # print("3") + session.add(data( + data = insertData, + )) + # print("4") + + session.commit() #Attempt to commit all the records + # print("good") + return 0 + except Exception as e: + print(f"Error {e} when sending to mariadb") + session.rollback() #Rollback the changes on error + return 1 + + +def tempsnsrIntoFluxQLDB( + inpayload: Annotated[SensorPayload, "Payload"] + ) -> int: + + """ + Insert temperature data into InfluxDB + :inpayload: Payload + :return: 0 if succesfull, 1 if general error, 2 if Influx is not enabled + """ + if not USEINFLUX: + return 2 + # Prep InfluxDB data + inflxdb_Data_To_Send = ( + influxdb_client.Point(f"{INFXLUXDB_MEASUEREMENT}") + .tag("PLACE", inpayload.name) + .tag("TEMP", sensorIDinp) + .tag("TEMP", whatTheSensor) + .field("TEMP", inpayload.temp) + .field("TEMP", inpayload.humid) + .field("TEMP", inpayload.hicc) + .field("TEMP", inpayload.presss) + .field("TEMP", inpayload.alttd) + ) + + # print(write_fluxdb_api.write(bucket=INFLUXBCKT, org=INFLUXORG, record=inflxdb_Data_To_Send)) + # if write_fluxdb_api.write(bucket=INFLUXBCKT, org=INFLUXORG, record=inflxdb_Data_To_Send) == None: + + if write_fluxdb_api.write(bucket=INFLUXBCKT, org=INFLUXORG, record=inflxdb_Data_To_Send): + # return {"STATUS": "succesfully inserted to InfluxDB"} + print("0") + return 0 + else: + print("1") + return 1 diff --git a/cont/code/main.py b/cont/code/main.py new file mode 100644 index 0000000..f495234 --- /dev/null +++ b/cont/code/main.py @@ -0,0 +1,68 @@ +# import pysnmp, sqlalchemy, ipaddress, os, influxdb_client, re, time +# # SQL +# from sqlalchemy import create_engine, exc # , Column, Integer, String, Numeric +# from sqlalchemy.ext.declarative import declarative_base +# from sqlalchemy.orm import sessionmaker +# # InfluxDB +# from influxdb_client import InfluxDBClient, Point, WritePrecision +# from influxdb_client.client.write_api import SYNCHRONOUS, ASYNCHRONOUS, WriteOptions +import pysnmp, ipaddress, os, re, time +#SNMP +from easysnmp import Session +# QoL +from typing import Annotated + + +# Program ENV--------------------------------------- +HOST_LIST: Final[list] = os.getenv("HOST_LIST") +try: + RESOLV_ADDR: Final[str] = ipaddress(os.getenv("RESOLV_ADDR", "1.1.1.1")) +except ValueError: + raise Exception("Malformed DNS IP address.\nExiting...") + + +# Flightchecks------------------------------------------- +# Helper functions +def check_host_string(input_text: Annotated[str, "Text to check"]): + pattern = re.compile(r"^[A-Za-z].*$", re.IGNORECASE) + return pattern.match(input_text) + + +# Check if some neccesary ENVs are passed +if not USEINFLUX and not USESQL: + raise Exception("No database selected to store the data") + +# if HOST_LIST empty, raise Exception No hosts passed +if not HOST_LIST: + raise Exception("No hosts passed\nExiting...") + +# for host in HOST_LIST check if valid IP and create a list with strings +HOST_LIST_STR = [] +for host in HOST_LIST: + try: + if not check_host_string(host): + ip = str(ipaddress.IPv4Address(host)) + HOST_LIST_STR.append(ip) + except ValueError: + raise Exception("Malformed IP address.\nExiting...") + +# Checking if DNS IP is a valid one +# Done under "Program ENV" on line ~18 + + + + + + +# Code------------------------------------------- + +# Create connections for MySQL and InfluxDB + +# functions for inserting data into two DBs + + +# DNS lookup if the user passed a hostname +# if not given a specific IP for DNS server, fallback to 1.1.1.1 + +# maybe a class? +# store last value and check if it wasn't sent already to the database diff --git a/cont/conf/Dockerfile b/cont/conf/Dockerfile new file mode 100644 index 0000000..1d135ba --- /dev/null +++ b/cont/conf/Dockerfile @@ -0,0 +1,40 @@ +FROM alpine:latest + +# https://docs.docker.com/reference/dockerfile/#environment-replacement +# ENV PUID=1600 +# ENV USER=itsme + +# VOLUME [""] + +RUN apk update && \ + apk add python3 py3-pip su-exec curl && \ + mkdir -p /app/snmpython/ + +WORKDIR /app/snmpython/ + +COPY ../code/someMain.py /app/snmpython/ + +# COPY ./entrypoint.sh /entrypoint.sh +# RUN chmod +x /entrypoint.sh + +# Add user to run under +# RUN adduser --disabled-password -u "${PUID}" "${USER}" +# RUN chown -R "${USER}" /app/snmpython + + +COPY ./requirements.txt /app/snmpython/ + +RUN python3 -m venv venv && \ + venv/bin/python3 -m pip install --upgrade pip && \ + venv/bin/pip3 install -r requirements.txt + # venv/bin/pip3 install -r requirements.txt + +# Set user. No need for root past this point +# USER "${USER}" + +# ENTRYPOINT ["/entrypoint.sh"] + +# HEALTHCHECK --interval=3s --timeout=3s --retries=3 \ +# CMD curl --fail http://127.0.0.1:8181/health || exit 1 + +CMD ["/app/snmpython/someMain.py"] diff --git a/cont/conf/build.sh b/cont/conf/build.sh new file mode 100755 index 0000000..d94c6b3 --- /dev/null +++ b/cont/conf/build.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +docker buildx build --platform linux/amd64 --tag tea.shupogaki.org/yuruc3/snmpython:v0 --debug --push . diff --git a/cont/conf/docker-compose.dev.yaml b/cont/conf/docker-compose.dev.yaml new file mode 100644 index 0000000..a3e1192 --- /dev/null +++ b/cont/conf/docker-compose.dev.yaml @@ -0,0 +1,16 @@ +--- + +services: + mdfanchanger: + container_name: snmpython-dev + user: 1600:1600 + image: shupotea/yuruc3/snmpython:dev + build: + dockerfile: ./Dockerfile + environment: + - INFLUXTOKEN: "12345678" + - INFLUXHOST: "" + + + - HOST_LIST: ["192.168.0.1", "192.168.0.1", "192.168.0.1", "192.168.0.1"] + restart: unless-stopped diff --git a/cont/conf/entrypoint.sh b/cont/conf/entrypoint.sh new file mode 100644 index 0000000..0b54751 --- /dev/null +++ b/cont/conf/entrypoint.sh @@ -0,0 +1,25 @@ +#!/bin/sh +set -e + +# Default to 1600 if not provided +PUID="${PUID:-1000}" +PGID="${PGID:-1000}" +USER="${USER:-pythusr}" +GROUP="${USER:-pythusr}" +CHOWNPATH="/app/snmpython" + +# Create group if missing +if ! getent group "$GROUP" >/dev/null 2>&1; then + addgroup -g "$PGID" "$GROUP" +fi + +# Create user if missing +if ! id -u "$USER" >/dev/null 2>&1; then + adduser -D -u "$PUID" -G "$GROUP" "$USER" +fi + +# Fix permissions (only do this on /app/API) +chown -R "$PUID:$PGID" "$CHOWNPATH" + +# Drop privileges & run command +exec su-exec "$PUID:$PGID" "$@" diff --git a/cont/conf/requirements.txt b/cont/conf/requirements.txt new file mode 100644 index 0000000..c538c79 --- /dev/null +++ b/cont/conf/requirements.txt @@ -0,0 +1,12 @@ +# mysql-connector-python==9.2.0 +PyMySQL==1.1.1 +sqlmodel==0.0.24 +pydantic==2.10.6 +pydantic_core==2.27.2 +annotated-types==0.7.0 +typing==3.7.4.3 +pysnmp==7.1.22 + +# requests==2.32.3 +influxdb-client==1.49.0 +# influxdb3-python==0.18.0 \ No newline at end of file