From 094139f79616b174328433d17a0cf86f4db6de86 Mon Sep 17 00:00:00 2001 From: Luca Lutz Date: Tue, 1 Nov 2022 16:40:17 +0100 Subject: [PATCH] Stop watching --- Dockerfile | 6 + Dockerfile-DB | 2 + FabAccess.sql | 5 + docker-compose.yml | 536 +++++++++++++++++++++++++++++++++++++++++++++ main.py | 178 +++++++++++++++ 5 files changed, 727 insertions(+) create mode 100644 Dockerfile create mode 100644 Dockerfile-DB create mode 100644 FabAccess.sql create mode 100644 docker-compose.yml create mode 100644 main.py diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4903558 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM python:3.11.0a3-bullseye +RUN pip install keycloak_wrapper paho-mqtt python-keycloak mysql.connector +RUN mkdir /app/ +COPY main.py /app/ +ENTRYPOINT [ "python" ] +CMD [ "-u", "/app/main.py" ] \ No newline at end of file diff --git a/Dockerfile-DB b/Dockerfile-DB new file mode 100644 index 0000000..ae58143 --- /dev/null +++ b/Dockerfile-DB @@ -0,0 +1,2 @@ +FROM mysql:5.7 +ADD FabAccess.sql /docker-entrypoint-initdb.d \ No newline at end of file diff --git a/FabAccess.sql b/FabAccess.sql new file mode 100644 index 0000000..4cfdb37 --- /dev/null +++ b/FabAccess.sql @@ -0,0 +1,5 @@ +DROP DATABASE IF EXISTS FabAccess; +CREATE DATABASE IF NOT EXISTS FabAccess; +USE FabAccess; +CREATE TABLE `ReaderPlug` (`ReaderID` INT NOT NULL , `PlugName` VARCHAR(255) NOT NULL, `PermissionPath` VARCHAR(255) NOT NULL, `Status` BOOLEAN NOT NULL, `LastUser` VARCHAR(255) NOT NULL) ENGINE = InnoDB; +INSERT INTO `ReaderPlug` (`ReaderID`, `PlugName`, `PermissionPath`, `Status`, `LastUser`) VALUES ("001", "lasercutter", "sfz.lasercutter.trotec", 0, "luca.lutz"); \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9454629 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,536 @@ +version: '3' + +networks: + auth: + FabDB: + FabLoggingDB: + FabLoggingTelegraf: + reverse-proxy: + mediawiki: + nextcloud: + gitea: + octofarm: + octoproxy: + ipam: + config: + - subnet: 172.42.0.0/24 + odoo: + roundcube: + partdb: + +volumes: + build-trigger: + build-output: + db: + nextcloud: + +secrets: + samba-admin-password: + file: ./samba-admin-password + +services: + AD: + image: instantlinux/samba-dc:latest + restart: always + network_mode: host + cap_add: + - CAP_SYS_ADMIN + hostname: dc.sfz.lab + environment: + DOMAIN_ACTION: provision + INTERFACES: -lo eth0 + REALM: ad.sfz.lab + TZ: Europe/Berlin + WORKGROUP: AD + volumes: + - ./data/samba/config:/etc/samba + - ./data/samba/data:/var/lib/samba + secrets: + - samba-admin-password + + reverse-proxy: + image: nginx-openid + build: + context: ./data/reverse-proxy/ + dockerfile: Dockerfile + ports: + - "80:80" + - "443:443" + volumes: + - ./data/reverse-proxy/content/:/usr/share/nginx/html/ + - ./data/reverse-proxy/config/:/etc/nginx/conf.d/ + - /etc/letsencrypt:/etc/nginx/certs + - /etc/localtime:/etc/localtime:ro + - ./data/mirror/config/:/mirror/config_web/ + networks: + reverse-proxy: + restart: unless-stopped + + + mysql: + image: mysql:5.7 + volumes: + - ./data/keycloak/DB/:/var/lib/mysql + environment: + MYSQL_RANDOM_ROOT_PASSWORD: "yes" + MYSQL_DATABASE: ${KEYCLOAK_DB_NAME} + MYSQL_USER: ${KEYCLOAK_DB_USER} + MYSQL_PASSWORD: ${KEYCLOAK_DB_PW} + networks: + auth: + + keycloak: + image: keycloak-sfz + build: + context: ./data/keycloak/ + dockerfile: Dockerfile + environment: + - DB_VENDOR=MYSQL + - DB_ADDR=mysql + - DB_DATABASE=${KEYCLOAK_DB_NAME} + - DB_USER=${KEYCLOAK_DB_USER} + - DB_PASSWORD=${KEYCLOAK_DB_PW} + - KEYCLOAK_USER=${KEYCLOAK_USER_NAME} + - KEYCLOAK_PASSWORD=${KEYCLOAK_USER_PW} + - PROXY_ADDRESS_FORWARDING=true + depends_on: + - mysql + networks: + auth: + reverse-proxy: + volumes: + - ./data/keycloak/data/:/lib/jvm/jre-11/lib/security/ + - ./data/keycloak/cert/:/etc/pki/java/ + + + mosquitto: + image: hivemq/hivemq4 + ports: + - 1883:1883 + - 9001:9001 + networks: + - FabDB + + FabBackend: + image: fabbackend + build: + context: ./data/FabBackend + dockerfile: Dockerfile + environment: + KEYCLOAK_USER_NAME: ${KEYCLOAK_USER_NAME} + KEYCLOAK_USER_PW: ${KEYCLOAK_USER_PW} + KEYCLOAK_REALM: ${KEYCLOAK_REALM} + FABDB_DB_USER_NAME: ${FABDB_DB_USER_NAME} + FABDB_DB_USER_PW: ${FABDB_DB_USER_PW} + FABDB_DB_NAME: ${FABDB_DB_NAME} + networks: + - FabDB + - auth + restart: unless-stopped + + FabDB: + image: fabdb + build: + context: ./data/FabBackend + dockerfile: Dockerfile-DB + volumes: + - ./data/FabBackend/DB/:/var/lib/mysql + environment: + MYSQL_RANDOM_ROOT_PASSWORD: "yes" + MYSQL_DATABASE: ${FABDB_DB_NAME} + MYSQL_USER: ${FABDB_DB_USER_NAME} + MYSQL_PASSWORD: ${FABDB_DB_USER_PW} + networks: + - FabDB + + FabLoggingDB: + image: influxdb:1.5 + volumes: + - ./data/FabLogging/DB/data/:/var/lib/influxdb/ + - ./data/FabLogging/DB/config/:/etc/influxdb/ + restart: always + networks: + - FabLoggingDB + - FabLoggingTelegraf + + FabLoggingDBTelegraf: + image: telegraf + volumes: + - ./data/FabLogging/telegraf/telegraf.conf:/etc/telegraf/telegraf.conf + - /var/run/docker.sock:/var/run/docker.sock + restart: always + user: "0" + networks: + - FabDB + - FabLoggingTelegraf + + grafana: + image: grafana/grafana + user: "0" + #ports: + # - "3000:3000" + volumes: + - ./data/FabLogging/data/grafana:/var/lib/grafana + restart: always + networks: + - FabLoggingDB + + buildserver-worker: + image: buildserver + build: + context: ./data/buildserver/worker/ + dockerfile: Dockerfile + volumes: + - build-trigger:/trigger/ + - build-output:/output/ + - /etc/localtime:/etc/localtime:ro + restart: unless-stopped + + buildserver-web-trigger: + image: buildserver-web-trigger + build: + context: ./data/buildserver/trigger/ + dockerfile: Dockerfile + networks: + reverse-proxy: + volumes: + - build-trigger:/var/www/trigger/ + - /etc/localtime:/etc/localtime:ro + environment: + - DEBUG=true + - HISTCONTROL=ignoredups + restart: unless-stopped + + buildserver-web-server: + image: httpd:latest + networks: + reverse-proxy: + volumes: + - build-output:/usr/local/apache2/htdocs/ + - /etc/localtime:/etc/localtime:ro + restart: unless-stopped + + mediawiki: + image: mediawikisfz + build: + dockerfile: Dockerfile + context: ./data/mediawiki/ + networks: + reverse-proxy: + mediawiki: + auth: + depends_on: + - mediawiki-mysql + restart: unless-stopped + volumes: + - ./data/mediawiki/images:/var/www/html/images/ + + mediawiki-mysql: + image: mariadb + volumes: + - ./data/mediawiki/DB:/var/lib/mysql + environment: + MYSQL_ROOT_PASSWORD: ${MEDIAWIKIDB_ROOT_PW} + networks: + mediawiki: + hostname: mediawiki-mysql + restart: unless-stopped + + cdn01: + image: httpd + networks: + reverse-proxy: + volumes: + - ./data/CDN:/usr/local/apache2/htdocs/ + restart: unless-stopped + + cdn02: + image: httpd + networks: + reverse-proxy: + volumes: + - ./data/CDN:/usr/local/apache2/htdocs/ + restart: unless-stopped + + cdn03: + image: httpd + networks: + reverse-proxy: + volumes: + - ./data/CDN:/usr/local/apache2/htdocs/ + restart: unless-stopped + + db: + image: mariadb:10.5 + command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW + restart: always + volumes: + - db:/var/lib/mysql + networks: + nextcloud: + environment: + - MYSQL_RANDOM_ROOT_PASSWORD="yes" + - MYSQL_PASSWORD=${NEXTCLOUD_MYSQL_PW} + - MYSQL_DATABASE=nextcloud + - MYSQL_USER=nextcloud + + redis: + image: redis:alpine + restart: always + networks: + nextcloud: + + app: + image: nextcloud:fpm-alpine + restart: always + volumes: + - nextcloud:/var/www/html + environment: + - MYSQL_HOST=db + - REDIS_HOST=redis + - MYSQL_PASSWORD=${NEXTCLOUD_MYSQL_PW} + - MYSQL_DATABASE=nextcloud + - MYSQL_USER=nextcloud + depends_on: + - db + - redis + networks: + nextcloud: + + web: + build: ./data/nextcloud/ + restart: always + volumes: + - nextcloud:/var/www/html:ro + depends_on: + - app + networks: + reverse-proxy: + nextcloud: + + cron: + image: nextcloud:fpm-alpine + restart: always + volumes: + - nextcloud:/var/www/html + entrypoint: /cron.sh + depends_on: + - db + - redis + + gitea: + image: gitea/gitea:1.15.9 + environment: + - USER_UID=1000 + - USER_GID=1000 + - GITEA__database__DB_TYPE=mysql + - GITEA__database__HOST=git-db:3306 + - GITEA__database__NAME=gitea + - GITEA__database__USER=gitea + - GITEA__database__PASSWD=${GIT_DB_PW} + restart: always + networks: + - gitea + - reverse-proxy + volumes: + - ./data/gitea/data:/data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + depends_on: + - git-db + + git-db: + image: mysql:8 + restart: always + environment: + - MYSQL_RANDOM_ROOT_PASSWORD="yes" + - MYSQL_USER=gitea + - MYSQL_PASSWORD=${GIT_DB_PW} + - MYSQL_DATABASE=gitea + networks: + - gitea + volumes: + - ./data/gitea/db:/var/lib/mysql + + octofarm-db: + image: mongo:4.4 + environment: + MONGO_INITDB_ROOT_USERNAME: octofarm + MONGO_INITDB_ROOT_PASSWORD: ${OCTOFARM_DB_PW} + MONGO_INITDB_DATABASE: octofarm + volumes: + - ./data/OctoFarm/data/mongodb-data:/data/db + restart: unless-stopped + networks: + octofarm: + + octofarm: + image: octofarm/octofarm:latest + restart: unless-stopped + mem_limit: 400m # Feel free to adjust! 400 MB is quite high and a safety limit. + networks: + reverse-proxy: + octofarm: + octoproxy: + ipv4_address: 172.42.0.3 + environment: + - MONGO=mongodb://octofarm:${OCTOFARM_DB_PW}@octofarm-db:27017/octofarm?authSource=admin + ports: + - 4000:4000 + expose: + - 4000 + volumes: + - ./data/OctoFarm/logs:/app/logs + - ./data/OctoFarm/scripts:/app/scripts + - ./data/OctoFarm/images:/app/images + - ./data/OctoFarm/hosts:/etc/hosts:ro + + octoproxy: + image: nginx:latest + volumes: + - ./data/octoproxy/config/:/etc/nginx/conf.d/ + - /etc/localtime:/etc/localtime:ro + - /etc/letsencrypt:/etc/nginx/certs + networks: + octoproxy: + ipv4_address: 172.42.0.2 + restart: unless-stopped + + octostreamer: + image: gersilex/cvlc + command: rtsp://admin:@10.10.42.60 --sout '#transcode{vcodec=MJPG,venc=ffmpeg{strict=1}}:standard{access=http{mime=multipart/x-mixed-replace;boundary=--7b3cc56e5f51db803f790dad720ed50a},mux=mpjpeg,dst=:8080/}' + networks: + reverse-proxy: + environment: + - RS_SNAPSHOT_INTERVAL=1000 + + octorestreamer: + image: datarhei/restreamer:latest + restart: always + networks: + reverse-proxy: + environment: + - RS_USERNAME=admin + - RS_PASSWORD=${OCTORESTREAMER_PW} + ports: + - 8087:8080 + volumes: + - ./data/restreamer/db:/restreamer/db + +# docker-zabbix-agent: +# restart: always +# ports: +# - '10060:10050' +# volumes: +# - /etc/localtime:/etc/localtime:ro +# - /etc/timezone:/etc/timezone:ro +# environment: +# - ZBX_SERVER_HOST=172.21.0.1 +# - ZBX_HOSTNAME=USV +# image: apcupsd +# devices: +# - /dev/usb/hiddev0 +# build: +# context: ./data/usv/ +# dockerfile: Dockerfile + + docker-zabbix-agent2: + restart: always + ports: + - '10061:10050' + volumes: + - /etc/localtime:/etc/localtime:ro + - /etc/timezone:/etc/timezone:ro + environment: + - ZBX_SERVER_HOST=192.168.64.1 + - ZBX_HOSTNAME=SSL + image: zabbix/zabbix-agent2 + + odoo: + image: odoo-sfz + build: + context: ./data/odoo + dockerfile: Dockerfile + depends_on: + - odoo-db + environment: + - HOST=odoo-db + - USER=odoo + - PASSWORD=${ODOO_DB_PW} + networks: + reverse-proxy: + odoo: + volumes: + - ./data/odoo/addons:/var/lib/odoo/.local/share/Odoo/addons/14.0/ + - ./data/odoo/conf:/etc/odoo + - ./data/odoo/data:/var/lib/odoo + + odoo-db: + image: postgres:13 + environment: + - POSTGRES_DB=postgres + - POSTGRES_PASSWORD=${ODOO_DB_PW} + - POSTGRES_USER=odoo + networks: + odoo: + volumes: + - ./data/odoo/db:/var/lib/postgresql/data + + roundcubedb: + image: mysql:5.7 + container_name: roundcubedb + restart: unless-stopped + volumes: + - ./data/webmail/db/mysql:/var/lib/mysql + environment: + - MYSQL_ROOT_PASSWORD=${WEBMAIL_PW} + - MYSQL_DATABASE=roundcubemail + networks: + roundcube: + + documentserver: + restart: always + image: onlyoffice/documentserver + networks: + reverse-proxy: + + roundcubemail: + image: roundcube/roundcubemail:latest + container_name: roundcubemail + restart: unless-stopped + networks: + reverse-proxy: + roundcube: + depends_on: + - roundcubedb + volumes: + - ./data/webmail/www:/var/www/html + environment: + - ROUNDCUBEMAIL_DB_TYPE=mysql + - ROUNDCUBEMAIL_DB_HOST=roundcubedb + - ROUNDCUBEMAIL_DB_PASSWORD=${WEBMAIL_PW} + - ROUNDCUBEMAIL_SKIN=elastic + - ROUNDCUBEMAIL_DEFAULT_HOST=tls://mail.sfz-aalen.space + - ROUNDCUBEMAIL_SMTP_SERVER=tls://mail.sfz-aalen.space + + partdb-db: + image: mysql + command: --default-authentication-plugin=mysql_native_password + restart: always + networks: + partdb: + environment: + MYSQL_ROOT_PASSWORD: ${PARTDB_ROOT_PW} + + partdb: + container_name: partdb + # By default Part-DB will be running under Port 8080, you can change it here + image: jbtronics/part-db1:master + volumes: + # By default + - ./uploads:/var/www/html/uploads + - ./public_media:/var/www/html/public/media + restart: unless-stopped + networks: + reverse-proxy: + partdb: diff --git a/main.py b/main.py new file mode 100644 index 0000000..9ce93f7 --- /dev/null +++ b/main.py @@ -0,0 +1,178 @@ +from paho.mqtt import client as mqtt_client +from keycloak import KeycloakAdmin +import json +import mysql.connector +import os + +broker = 'mosquitto' +port = 1883 +client_id = f'FabMan' + +KEYCLOAK_URL = "http://keycloak:8080/auth/" +KEYCLOAK_USERNAME = os.environ['KEYCLOAK_USER_NAME'] +KEYCLOAK_PASSWORD = os.environ['KEYCLOAK_USER_PW'] +REALM = os.environ['KEYCLOAK_REALM'] + +FabDB = mysql.connector.connect( + host="FabDB", + user=os.environ['FABDB_DB_USER_NAME'], + password=os.environ['FABDB_DB_USER_PW'], + database=os.environ['FABDB_DB_NAME'] +) + +def connect_mqtt(): + def on_connect(client, userdata, flags, rc): + if rc == 0: + print("Connected to MQTT Broker!") + else: + print("Failed to connect, return code %d\n", rc) + + client = mqtt_client.Client(client_id) + client.username_pw_set("admin", "user") + client.on_connect = on_connect + client.connect(broker, port) + return client + +def publish(client,topic,msg): + result = client.publish(topic, msg) + # result: [0, 1] + status = result[0] + if status == 0: + #print(f"Send `{msg}` to topic `{topic}`") + pass + else: + print(f"Failed to send message to topic {topic}") + +def changePlug(client,PlugID,state): + publish(client,f"/FabLogging/{PlugID}/POWER",state) + cmd = ["OFF", "On"][state] + publish(client,f"/aktoren/{PlugID}/cmnd/POWER",cmd) + +def changeDisplay(client,MachineID,status,text): + publish(client,f"/cmnd/reader/{MachineID}",'{"Cmd": "message", "MssgID": %s , "ClrTxt":"" , "AddnTxt":"%s"}' % ((status), (text))) + +def checkPermission(UserPermsJSON,PermissionPath): + permission = False + error = 0 + MachinePermArray = PermissionPath.split(".") + for UserPerm in UserPermsJSON: + #print(f"check {UserPerm}") + UserPermArray = UserPerm.split(".") + x = 0 + for UserPermPart in UserPermArray: + if(error == 0): + #print(f"Compare {UserPermPart} and {MachinePermArray[x]}") + if not (MachinePermArray[x] == UserPermPart): + if(UserPermPart == "*"): + #print("* regelt") + permission = True + else: + error = 1 + #print(f"MISmatch between {MachinePermArray[x]} and {UserPermPart}") + else: + pass + #print(f"Match between {MachinePermArray[x]} and {UserPermPart}") + x = x + 1 + if(error == 1): + pass + #print("Error") + else: + #print("Hurra") + permission = True + error = 0 + return permission + +def msg_handler(msg,client): + print("") + print(f"Received `{msg.payload.decode()}` from `{msg.topic}` topic") + topic = msg.topic + MQTT_KEY = json.loads(msg.payload.decode())["UID"] + keycloak_admin = KeycloakAdmin( server_url=KEYCLOAK_URL, username=KEYCLOAK_USERNAME, password=KEYCLOAK_PASSWORD, realm_name=REALM, verify=True) + users = keycloak_admin.get_users({}) + knownUser = False + ReaderID = msg.topic.split("/")[-1] + for user in users: + try: + FabCardID = user['attributes']['FabCard'][0] + UserPermissions = user['attributes']['FabPermissions'][0] + if (FabCardID == MQTT_KEY): + knownUser = True + print(f"FabCard matches with user {user['username']}") + + mycursor = FabDB.cursor() + mycursor.execute(f'SELECT PlugName,PermissionPath,Status,LastUser FROM ReaderPlug WHERE ReaderID="{ReaderID}";') + myValues = mycursor.fetchall()[0] + PlugID = myValues[0] + PermissionPath = myValues[1] + MachineStatus = myValues[2] + LastUser = myValues[3] + #print(PermissionPath) + UserPermsJSON = json.loads(UserPermissions) + permission = checkPermission(UserPermsJSON, PermissionPath) + print('System "use" access %s' % ['denied','granted'][permission]) + + if(permission): + error = False + status = MachineStatus + ActualUser = user['username'] + if(status): + status = False + if(LastUser == ActualUser): + error = False + else: + try: + if (keycloak_admin.get_user_groups(user_id=user['id'])[0]['name'] == 'Mentoren'): + print('Overrided becouse of "Mentor" Group') + else: + print(f'Bereits benutzt von {LastUser}') + error = True + except IndexError: + print("No groups available") + error = True + if(error): + changeDisplay(client, ReaderID, 9, LastUser) + else: + status = True + error = False + if(error == False): + #KSstatus ^= True + switch = ["off", "on"][status] + print(f"Turn Plug {switch}") + #print(status) + changePlug(client, PlugID, status) + publish(client,f"/FabLogging/{PlugID}/USER",ActualUser) + try: + firstCombo = user['firstName']+" " + DisplayUser = firstCombo+user['lastName'] + if(len(firstCombo) >= 7): + DisplayUser = user['firstName'][:7] + except KeyError: + DisplayUser = user['username'] + if(len(DisplayUser) > 7): + DisplayUser = DisplayUser[0:7] + "." + DisplayUser[7+1: ] + DisplayUser = DisplayUser[:8] + changeDisplay(client, ReaderID, [20, 20][status], ["Bitte anmelden", f"Login\n{DisplayUser}"][status]) + mycursor.execute(f'UPDATE ReaderPlug SET Status={status}, LastUser="{ActualUser}" WHERE ReaderID="{ReaderID}";') + FabDB.commit() + else: + changeDisplay(client, ReaderID, 7, "") + except KeyError: + # Key is not present + pass + if not (knownUser): + changeDisplay(client, ReaderID, 16, MQTT_KEY) + +def subscribe(client: mqtt_client, topic): + def on_message(client, userdata, msg): + msg_handler(msg,client) + + client.subscribe(topic) + client.on_message = on_message + +def run(): + client = connect_mqtt() + subscribe(client,"/rfid_reader/#") + client.loop_forever() + +if __name__ == '__main__': + run() \ No newline at end of file