diff --git a/.github/workflows/images-publish.yml b/.github/workflows/images-publish.yml index 6208d2d3..b2aaeb7c 100644 --- a/.github/workflows/images-publish.yml +++ b/.github/workflows/images-publish.yml @@ -2,9 +2,12 @@ name: Build and Push Docker Images on: push: - branches: [master] + branches: [master, dev] paths: - - "images/**" + - "tsdb/**" + - "trackeroo-backend/**" + - "brokeroo/**" + - "flink_job/**" workflow_dispatch: jobs: @@ -22,7 +25,13 @@ jobs: list-files: shell filters: | tsdb: - - 'images/tsdb/**' + - 'tsdb/**' + trackeroo-backend: + - "trackeroo-backend/**" + brokeroo: + - "brokeroo/**" + flink_job: + - "flink_job/**" build: needs: detect @@ -30,7 +39,19 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - image: ${{ fromJSON(needs.detect.outputs.images) }} + include: + - image: tsdb + context: ./tsdb + dockerfile: ./tsdb/Dockerfile + - image: trackeroo-backend + context: ./trackeroo-backend/src + dockerfile: ./trackeroo-backend/src/Dockerfile + - image: brokeroo + context: ./brokeroo + dockerfile: ./brokeroo/Dockerfile + - image: flink_job + context: ./flink_job + dockerfile: ./flink_job/Dockerfile permissions: contents: read packages: write @@ -47,13 +68,14 @@ jobs: - name: Read version.yml id: version run: | - VERSION=$(yq e '.version' images/${{ matrix.image }}/version.yml) + VERSION=$(yq e '.version' ${{ matrix.context }}/version.yml) echo "version=$VERSION" >> $GITHUB_OUTPUT - name: Build and Push Docker image uses: docker/build-push-action@v6 with: - context: ./images/${{ matrix.image }} + context: ${{ matrix.context }} + file: ${{ matrix.dockerfile }} push: true tags: | ghcr.io/${{ github.repository_owner }}/${{ matrix.image }}:${{ steps.version.outputs.version }} diff --git a/.gitignore b/.gitignore index aa4a1cd3..4629a8e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ *.qcow2 *.pem +*.class +*.bin +*.lock +flink_job/.gradle secrets .pytest_cache __pycache__ @@ -11,7 +15,12 @@ tdevice.json claim.py status brockeroo -grafana +trackeroo-backend/grafana trackeroo-backend/mongo/data nominatim-data osrm-data +trackeroo-backend/redis +mqtt_client +tracky/docker-compose.yml +grafana.json +.project diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..7b016a89 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.compile.nullAnalysis.mode": "automatic" +} \ No newline at end of file diff --git a/bootstrap/cloud-init/nodes/user-data.yaml b/bootstrap/cloud-init/nodes/user-data.yaml index 28ae9065..246395ed 100644 --- a/bootstrap/cloud-init/nodes/user-data.yaml +++ b/bootstrap/cloud-init/nodes/user-data.yaml @@ -5,7 +5,7 @@ locale: en_US.UTF-8 # User configuration users: - name: k3s-admin - passwd: $6$rounds=4096$v0k4/WEZZLQnikFD$1b5obdvCiCz.BffcjSUPS4MTPV7Ro6cr8J87HWpyonOQX7rX823rap/1mk/.plRFnMlhOvGLkuoWrgfCArZOb. + passwd: $6$rounds=4096$hctGKNXeELHL1wkO$Vqml85LbOZvpBoLjbU2Se77TLVVcX2GhP5MHXtF/amhAGSM36x7xviZzmBEkfoa06m6N3ZUCUkIsAid6myFvl. lock_passwd: false groups: sudo shell: /bin/bash diff --git a/bootstrap/cloud-init/servers/user-data.yaml b/bootstrap/cloud-init/servers/user-data.yaml index 8f00a6ab..3603e1ed 100644 --- a/bootstrap/cloud-init/servers/user-data.yaml +++ b/bootstrap/cloud-init/servers/user-data.yaml @@ -5,7 +5,7 @@ locale: en_US.UTF-8 # User configuration users: - name: k3s-admin - passwd: $6$rounds=4096$v0k4/WEZZLQnikFD$1b5obdvCiCz.BffcjSUPS4MTPV7Ro6cr8J87HWpyonOQX7rX823rap/1mk/.plRFnMlhOvGLkuoWrgfCArZOb. + passwd: $6$rounds=4096$B9SFui0p6YaAGZAF$PVHzzOun0HNCAOY4G6aJs.2EqXNwJiD3CE4/IFlaUs6e6HNGDl0CzpTmzkwTsBCUD4DLt5bFvn2q4fW5nFKBS0 lock_passwd: false groups: sudo shell: /bin/bash diff --git a/bootstrap/cloud-init/specs.yaml b/bootstrap/cloud-init/specs.yaml index 1ea00ae2..b6a204e8 100644 --- a/bootstrap/cloud-init/specs.yaml +++ b/bootstrap/cloud-init/specs.yaml @@ -1,7 +1,7 @@ - machine_type: server name: k3s-server-cronus - cpu: 4 - ram: 4096 + cpu: 2 + ram: 2048 os_storage: 20 longhorn_storage: 0 networks: @@ -15,8 +15,8 @@ mac: 52:54:00:19:b9:c4 - machine_type: server name: k3s-server-hyperion - cpu: 4 - ram: 4096 + cpu: 2 + ram: 2048 os_storage: 20 longhorn_storage: 0 networks: @@ -30,8 +30,8 @@ mac: 52:54:00:ba:81:7a - machine_type: server name: k3s-server-oceanus - cpu: 4 - ram: 4096 + cpu: 2 + ram: 2048 os_storage: 20 longhorn_storage: 0 networks: @@ -45,8 +45,8 @@ mac: 52:54:00:9b:6e:0b - machine_type: node name: k3s-node-hermes - cpu: 6 - ram: 4096 + cpu: 8 + ram: 6144 os_storage: 20 longhorn_storage: 100 networks: @@ -60,8 +60,8 @@ mac: 52:54:00:36:8c:61 - machine_type: node name: k3s-node-achilles - cpu: 6 - ram: 4096 + cpu: 8 + ram: 6144 os_storage: 20 longhorn_storage: 100 networks: @@ -75,8 +75,8 @@ mac: 52:54:00:ca:d5:31 - machine_type: node name: k3s-node-odysseus - cpu: 6 - ram: 4096 + cpu: 8 + ram: 6144 os_storage: 20 longhorn_storage: 100 networks: diff --git a/bootstrap/k3s-ansible/playbooks/roles/k3s_agent/tasks/main.yml b/bootstrap/k3s-ansible/playbooks/roles/k3s_agent/tasks/main.yml index df950156..1c717053 100644 --- a/bootstrap/k3s-ansible/playbooks/roles/k3s_agent/tasks/main.yml +++ b/bootstrap/k3s-ansible/playbooks/roles/k3s_agent/tasks/main.yml @@ -16,6 +16,6 @@ curl -sfL https://get.k3s.io | K3S_URL="https://{{ kube_api_vip }}:6443" \ INSTALL_K3S_VERSION="{{ k3s_version }}" \ K3S_TOKEN="{{ leader_token.content | b64decode | trim }}" \ - sh -s - agent --node-ip {{ hostvars[inventory_hostname].internal_ip }} --kubelet-arg node-status-update-frequency=5s + sh -s - agent --node-ip {{ hostvars[inventory_hostname].internal_ip }} --kubelet-arg node-status-update-frequency=5s --node-label "workload-type=apps" args: creates: /etc/rancher/k3s diff --git a/bootstrap/k3s-ansible/playbooks/roles/k3s_server/templates/k3s_install.sh.j2 b/bootstrap/k3s-ansible/playbooks/roles/k3s_server/templates/k3s_install.sh.j2 index 13683a53..a47cf217 100644 --- a/bootstrap/k3s-ansible/playbooks/roles/k3s_server/templates/k3s_install.sh.j2 +++ b/bootstrap/k3s-ansible/playbooks/roles/k3s_server/templates/k3s_install.sh.j2 @@ -29,6 +29,10 @@ sh -s - ${K3S_ROLE} ${K3S_CLUSTER_INIT} ${K3S_SERVER} \ --tls-san {{ kube_api_vip }} \ --flannel-iface {{ internal_interface }} \ --disable-cloud-controller \ - --disable servicelb + --disable servicelb \ + --node-taint CriticalAddonsOnly=true:NoSchedule \ + # --node-taint "workload-type=apps" +# This is to prevent any workload from being scheduled on the servers. +# --node-taint CriticalAddonsOnly=true:NoSchedule \ echo "K3s server installation completed" diff --git a/bootstrap/k3s-ansible/playbooks/roles/longhorn/tasks/main.yml b/bootstrap/k3s-ansible/playbooks/roles/longhorn/tasks/main.yml index 3e2559bc..0881ed06 100644 --- a/bootstrap/k3s-ansible/playbooks/roles/longhorn/tasks/main.yml +++ b/bootstrap/k3s-ansible/playbooks/roles/longhorn/tasks/main.yml @@ -122,6 +122,7 @@ name: "{{ item }}" namespace: longhorn-system spec: + name: "{{ item }}" allowScheduling: false loop: "{{ groups['servers'] }}" diff --git a/bootstrap/k3s-ansible/playbooks/site.yml b/bootstrap/k3s-ansible/playbooks/site.yml index eeb13e42..04ccf782 100644 --- a/bootstrap/k3s-ansible/playbooks/site.yml +++ b/bootstrap/k3s-ansible/playbooks/site.yml @@ -21,6 +21,7 @@ - name: Deploy MetalLB and configure IP pool hosts: servers[0] + tags: metallb run_once: true roles: - metallb diff --git a/trackeroo-backend/brokeroo/Dockerfile b/brokeroo/Dockerfile similarity index 100% rename from trackeroo-backend/brokeroo/Dockerfile rename to brokeroo/Dockerfile diff --git a/trackeroo-backend/brokeroo/cmd/brokeroo/main.go b/brokeroo/cmd/brokeroo/main.go similarity index 54% rename from trackeroo-backend/brokeroo/cmd/brokeroo/main.go rename to brokeroo/cmd/brokeroo/main.go index a6b9cb9e..c1ae9373 100644 --- a/trackeroo-backend/brokeroo/cmd/brokeroo/main.go +++ b/brokeroo/cmd/brokeroo/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "database/sql" "encoding/json" "fmt" @@ -13,6 +14,7 @@ import ( mqtt "github.com/eclipse/paho.mqtt.golang" _ "github.com/lib/pq" + "github.com/segmentio/kafka-go" ) type Config struct { @@ -22,6 +24,7 @@ type Config struct { MQTTPassword string PostgresURL string TopicPattern string + KafkaBroker string } type MQTTData struct { @@ -32,14 +35,25 @@ type MQTTData struct { } type Service struct { - config *Config - mqttClient mqtt.Client - db *sql.DB + config *Config + mqttClient mqtt.Client + db *sql.DB + kafkaWriter *kafka.Writer + knownTopics map[string]bool /* for caching kafka topics */ +} + +type Envelope struct { + TsUnix int64 `json:"ts_unix"` + Ts string `json:"ts"` + DevID string `json:"dev_id"` + Tag string `json:"tag"` + PayloadJSON json.RawMessage `json:"payloadJson"` } func NewService(config *Config) *Service { return &Service{ - config: config, + config: config, + knownTopics: make(map[string]bool), } } @@ -57,13 +71,77 @@ func (s *Service) connectPostgres() error { return nil } +func (s *Service) connectKafka() error { + s.kafkaWriter = kafka.NewWriter(kafka.WriterConfig{ + Brokers: []string{s.config.KafkaBroker}, + Async: true, + }) + + if err := s.ensureTopicExists("health-check"); err != nil { + log.Printf("failed to create first topic for healt-check: %v", err) + } + + err := s.kafkaWriter.WriteMessages(context.Background(), + kafka.Message{ + Topic: "health-check", + Value: []byte("ping"), + }, + ) + + if err != nil { + log.Printf("Failed to write test message to Kafka: %v", err) + return err + } + + log.Println("Successfully connected to Kafka at", s.config.KafkaBroker) + return nil +} + +func (s *Service) ensureTopicExists(topic string) error { + + if s.knownTopics[topic] { + return nil + } + + conn, err := kafka.Dial("tcp", s.config.KafkaBroker) + if err != nil { + return fmt.Errorf("dial broker: %w", err) + } + defer conn.Close() + + controller, err := conn.Controller() + if err != nil { + return fmt.Errorf("get controller: %w", err) + } + + controllerConn, err := kafka.Dial("tcp", fmt.Sprintf("%s:%d", controller.Host, controller.Port)) + if err != nil { + return fmt.Errorf("dial controller: %w", err) + } + defer controllerConn.Close() + + err = controllerConn.CreateTopics(kafka.TopicConfig{ + Topic: topic, + NumPartitions: 1, + ReplicationFactor: 1, + }) + + if err != nil && !strings.Contains(err.Error(), "Topic with this name already exists") { + return fmt.Errorf("create topic %s: %w", topic, err) + } + + s.knownTopics[topic] = true + log.Printf("Topic pronto: %s", topic) + return nil +} + func (s *Service) connectMQTT() error { opts := mqtt.NewClientOptions() opts.AddBroker(s.config.MQTTBroker) opts.SetClientID(s.config.MQTTClientID) opts.SetUsername(s.config.MQTTUsername) opts.SetPassword(s.config.MQTTPassword) - opts.SetCleanSession(true) + opts.SetCleanSession(false) // Keep unacked messages opts.SetAutoReconnect(true) opts.SetKeepAlive(60 * time.Second) opts.SetPingTimeout(10 * time.Second) @@ -82,8 +160,18 @@ func (s *Service) connectMQTT() error { s.mqttClient = mqtt.NewClient(opts) - if token := s.mqttClient.Connect(); token.Wait() && token.Error() != nil { - return fmt.Errorf("failed to connect to MQTT broker: %w", token.Error()) + c := 0 + for { + if token := s.mqttClient.Connect(); token.Wait() && token.Error() != nil { + if c == 10 { + return fmt.Errorf("failed to connect to MQTT broker: %w", token.Error()) + } + c += 1 + log.Println("Connessione fallita, retry tra 2s:", token.Error()) + time.Sleep(2 * time.Second) + continue + } + break } log.Println("Successfully connected to MQTT broker") @@ -121,11 +209,6 @@ func (s *Service) messageHandler(client mqtt.Client, msg mqtt.Message) { log.Printf("Invalid JSON payload for topic %s: %v", topic, err) return } - var jsonPayload json.RawMessage - if err := json.Unmarshal(payload, &jsonPayload); err != nil { - log.Printf("Invalid JSON payload for topic %s: %v", topic, err) - return - } tsUnix, ok := data["ts"].(int64) if !ok { @@ -133,11 +216,51 @@ func (s *Service) messageHandler(client mqtt.Client, msg mqtt.Message) { } ts := time.Unix(tsUnix, 0).UTC() - + start := time.Now() + jsonPayload := json.RawMessage(payload) if err := s.insertData(tsUnix, ts, devID, tag, jsonPayload); err != nil { log.Printf("Failed to insert data: %v", err) return } + log.Printf("Time to write to DB %v", time.Since(start)) + start = time.Now() + /* kafka */ + kafkaTopic := sanitizeTopic(topic) + if err := s.ensureTopicExists(kafkaTopic); err != nil { + log.Printf("Errore creazione topic %s: %v", kafkaTopic, err) + return + } else { + log.Printf("Time to create topic %v", time.Since(start)) + start = time.Now() + envelope := Envelope{ + TsUnix: tsUnix, + Ts: ts.Format(time.RFC3339), // is this right? + DevID: devID, + Tag: tag, + PayloadJSON: payload, // il payload originale come stringa JSON + } + + envelopeBytes, err := json.Marshal(envelope) + if err != nil { + log.Printf("Failed to marshal envelope: %v", err) + return + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) /* 5 second timeout */ + defer cancel() + + err = s.kafkaWriter.WriteMessages(ctx, kafka.Message{ + Topic: kafkaTopic, + Value: envelopeBytes, + }) + + if err != nil { + log.Printf("Failed to write to Kafka: %v", err) + } else { + log.Printf("Message forwarded to Kafka topic: %s", kafkaTopic) + } + log.Printf("Time to write to Kafka %v", time.Since(start)) + } log.Printf("Successfully inserted data for dev_id: %s, tag: %s", devID, tag) } @@ -164,6 +287,10 @@ func (s *Service) Start() error { return err } + // Connect to Kafka + if err := s.connectKafka(); err != nil { + return err + } return nil } @@ -180,16 +307,22 @@ func (s *Service) Stop() { s.db.Close() log.Println("Closed PostgreSQL connection") } + + if s.kafkaWriter != nil { + s.kafkaWriter.Close() + log.Println("Closed Kafka writer") + } } func loadConfig() *Config { return &Config{ MQTTBroker: getEnvOrDefault("MQTT_BROKER", "tcp://localhost:1883"), - MQTTClientID: getEnvOrDefault("MQTT_CLIENT_ID", "go-mqtt-postgres-service"), + MQTTClientID: getEnvOrDefault("MQTT_CLIENT_ID", "brokeroo"), MQTTUsername: getEnvOrDefault("MQTT_USERNAME", ""), MQTTPassword: getEnvOrDefault("MQTT_PASSWORD", ""), PostgresURL: getEnvOrDefault("POSTGRES_URL", "postgres://user:password@localhost/dbname?sslmode=disable"), TopicPattern: getEnvOrDefault("TOPIC_PATTERN", "j/data/+/+"), + KafkaBroker: getEnvOrDefault("KAFKA_BROKER", "kafka:9092"), } } @@ -200,8 +333,12 @@ func getEnvOrDefault(key, defaultValue string) string { return defaultValue } +func sanitizeTopic(topic string) string { + return strings.ReplaceAll(topic, "/", "-") +} + func main() { - log.Println("Starting MQTT to PostgreSQL service...") + log.Println("Starting MQTT to Kafka and PostgreSQL service...") config := loadConfig() service := NewService(config) diff --git a/trackeroo-backend/brokeroo/go.mod b/brokeroo/go.mod similarity index 52% rename from trackeroo-backend/brokeroo/go.mod rename to brokeroo/go.mod index 6f370a82..635eca98 100644 --- a/trackeroo-backend/brokeroo/go.mod +++ b/brokeroo/go.mod @@ -7,8 +7,14 @@ require ( github.com/lib/pq v1.10.9 ) +require ( + github.com/klauspost/compress v1.15.9 // indirect + github.com/pierrec/lz4/v4 v4.1.15 // indirect +) + require ( github.com/gorilla/websocket v1.5.3 // indirect - golang.org/x/net v0.27.0 // indirect + github.com/segmentio/kafka-go v0.4.49 + golang.org/x/net v0.38.0 // indirect golang.org/x/sync v0.7.0 // indirect ) diff --git a/trackeroo-backend/brokeroo/go.sum b/brokeroo/go.sum similarity index 54% rename from trackeroo-backend/brokeroo/go.sum rename to brokeroo/go.sum index 8570bddd..22bb7b8b 100644 --- a/trackeroo-backend/brokeroo/go.sum +++ b/brokeroo/go.sum @@ -2,9 +2,17 @@ github.com/eclipse/paho.mqtt.golang v1.5.0 h1:EH+bUVJNgttidWFkLLVKaQPGmkTUfQQqjO github.com/eclipse/paho.mqtt.golang v1.5.0/go.mod h1:du/2qNQVqJf/Sqs4MEL77kR8QTqANF7XU7Fk0aOTAgk= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= +github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/segmentio/kafka-go v0.4.49 h1:GJiNX1d/g+kG6ljyJEoi9++PUMdXGAxb7JGPiDCuNmk= +github.com/segmentio/kafka-go v0.4.49/go.mod h1:Y1gn60kzLEEaW28YshXyk2+VCUKbJ3Qr6DrnT3i4+9E= golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= diff --git a/trackeroo-backend/version.yaml b/brokeroo/version.yml similarity index 100% rename from trackeroo-backend/version.yaml rename to brokeroo/version.yml diff --git a/docker-compose-pull.yml b/docker-compose-pull.yml new file mode 100644 index 00000000..f4c8f3bd --- /dev/null +++ b/docker-compose-pull.yml @@ -0,0 +1,238 @@ +services: + trackeroo-backend: + image: ghcr.io/skiby7/trackeroo-backend:latest + container_name: trackeroo-backend + restart: no + healthcheck: + test: nc -z localhost 8080 + interval: 5s + timeout: 5s + retries: 5 + start_period: 10s + secrets: + - users_key + ports: + - "8080:8080" + depends_on: + mongo: + condition: service_healthy + redis: + condition: service_healthy + environment: + MONGO_URI: mongodb://mongo:27017/trackeroo + LOG_LEVEL: info + MQTT_HOST: localhost + MQTT_PORT: 1883 + MQTTS_PORT: 8883 + volumes: + - trackeroo-backend-data:/app/data + - ./trackeroo-backend/certs:/certs:ro + + redis: + container_name: redis + image: redis:8.2.1-alpine + volumes: + - redis-data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + restart: no + + mongo: + container_name: mongo + image: mongo:latest + restart: no + command: ["mongod", "--bind_ip_all"] + healthcheck: + test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] + interval: 5s + timeout: 5s + retries: 5 + start_period: 5s + volumes: + - mongo-data:/data/db + - ./mongo/init.js:/docker-entrypoint-initdb.d/init.js:ro + + mongo-express: + image: mongo-express:latest + container_name: mongo-express + restart: unless-stopped + ports: + - "8083:8081" + environment: + ME_CONFIG_MONGODB_SERVER: mongo + depends_on: + - mongo + + rabbitmq: + image: rabbitmq:3-management + restart: no + container_name: rabbitmq + ports: + - "5672:5672" + - "15672:15672" + - "1883:1883" + - "8883:8883" + environment: + RABBITMQ_DEFAULT_USER: admin + RABBITMQ_DEFAULT_PASS: admin123 + RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS: '-rabbitmq_mqtt tcp_listeners [{"0.0.0.0",1883}]' + volumes: + - ./rabbitmq/etc:/etc/rabbitmq/ + - rabbitmq-data:/var/lib/rabbitmq + - ./rabbitmq/etc:/etc/rabbitmq/ + command: > + bash -c "rabbitmq-plugins enable --offline rabbitmq_mqtt; + rabbitmq-server" + + tsdb: + image: ghcr.io/skiby7/tsdb:latest + container_name: tsdb + restart: no + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres -d tracker_db"] + interval: 10s + timeout: 5s + retries: 5 + command: ["postgres", "-c", "shared_preload_libraries=timescaledb"] + environment: + POSTGRES_DB: tracker_db + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_INITDB_ARGS: "--auth-host=scram-sha-256 --auth-local=scram-sha-256" + volumes: + - tsdb-data:/var/lib/postgresql/data + - ./tsdb/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d:ro + + pgadmin: + image: dpage/pgadmin4:latest + container_name: pgadmin + restart: no + environment: + PGADMIN_DEFAULT_EMAIL: admin@admin.com + PGADMIN_DEFAULT_PASSWORD: admin + ports: + - "8085:80" + volumes: + - pgadmin-data:/var/lib/pgadmin + depends_on: + tsdb: + condition: service_healthy + + brokeroo: + image: ghcr.io/skiby7/brokeroo:latest + container_name: brokeroo + restart: no + build: + context: brokeroo + dockerfile: Dockerfile + environment: + MQTT_BROKER: mqtt://rabbitmq:1883 + MQTT_CLIENT_ID: apps + MQTT_USERNAME: "apps" + MQTT_PASSWORD: "apps" + POSTGRES_URL: postgres://admin:administrator@tsdb:5432/tracker_db?sslmode=disable + TOPIC_PATTERN: j/data/+/+ + KAFKA_BROKER: kafka:9092 + depends_on: + kafka: + condition: service_started + tsdb: + condition: service_healthy + rabbitmq: + condition: service_started + + grafana: + image: grafana/grafana:latest + container_name: grafana + restart: no + depends_on: + tsdb: + condition: service_healthy + environment: + GF_SECURITY_ADMIN_USER: admin + GF_SECURITY_ADMIN_PASSWORD: admin123 + GF_DATABASE_TYPE: postgres + GF_DATABASE_HOST: tsdb:5432 + GF_DATABASE_NAME: tracker_db + GF_DATABASE_USER: admin + GF_DATABASE_PASSWORD: administrator + ports: + - "3000:3000" + volumes: + - grafana-data:/var/lib/grafana + + zookeeper: + container_name: zookeeper + image: confluentinc/cp-zookeeper:7.5.0 + environment: + ZOOKEEPER_CLIENT_PORT: 2181 + ZOOKEEPER_TICK_TIME: 2000 + + kafka: + container_name: kafka + image: confluentinc/cp-kafka:7.5.0 + ports: + - "9092:9092" + environment: + KAFKA_BROKER_ID: 1 + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT + KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT + depends_on: + - zookeeper + + flink-jobmanager: + image: flink:1.20-scala_2.12 + container_name: flink-jobmanager + ports: + - "8081:8081" # dashboard web di Flink + command: jobmanager + environment: + - | + FLINK_PROPERTIES= + jobmanager.rpc.address: flink-jobmanager + depends_on: + - kafka + - tsdb + + flink-taskmanager: + image: flink:1.20-scala_2.12 + container_name: flink-taskmanager + command: taskmanager + scale: 1 + environment: + - | + FLINK_PROPERTIES= + jobmanager.rpc.address: flink-jobmanager + taskmanager.numberOfTaskSlots: 2 + depends_on: + - flink-jobmanager + + flink-job: + image: ghcr.io/skiby7/flink_job:latest + container_name: flink-job + depends_on: + - flink-jobmanager + - flink-taskmanager + - kafka + - tsdb + environment: + - FLINK_PARALLELISM=2 + +volumes: + trackeroo-backend-data: + mongo-data: + rabbitmq-data: + tsdb-data: + pgadmin-data: + grafana-data: + redis-data: + +secrets: + users_key: + file: trackeroo-backend/secrets/users_key diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..b759b7ea --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,245 @@ +services: + trackeroo-backend: + container_name: trackeroo-backend + restart: no + build: + context: trackeroo-backend/src + dockerfile: Dockerfile + healthcheck: + test: nc -z localhost 8080 + interval: 5s + timeout: 5s + retries: 5 + start_period: 10s + secrets: + - users_key + ports: + - "8080:8080" + depends_on: + mongo: + condition: service_healthy + redis: + condition: service_healthy + environment: + MONGO_URI: mongodb://mongo:27017/trackeroo + LOG_LEVEL: debug + MQTT_HOST: localhost + MQTT_PORT: 1883 + MQTTS_PORT: 8883 + volumes: + - trackeroo-backend-data:/app/data + - ./trackeroo-backend/certs:/certs:ro + + redis: + container_name: redis + image: redis:8.2.1-alpine + volumes: + - redis-data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + restart: no + + mongo: + container_name: mongo + image: mongo:latest + restart: no + command: ["mongod", "--bind_ip_all"] + healthcheck: + test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] + interval: 5s + timeout: 5s + retries: 5 + start_period: 5s + volumes: + - mongo-data:/data/db + - ./mongo/init.js:/docker-entrypoint-initdb.d/init.js:ro + + mongo-express: + image: mongo-express:latest + container_name: mongo-express + restart: unless-stopped + ports: + - "8083:8081" + environment: + ME_CONFIG_MONGODB_SERVER: mongo + depends_on: + - mongo + + rabbitmq: + image: rabbitmq:3-management + restart: no + container_name: rabbitmq + ports: + - "5672:5672" + - "15672:15672" + - "1883:1883" + - "8883:8883" + environment: + RABBITMQ_DEFAULT_USER: admin + RABBITMQ_DEFAULT_PASS: admin123 + RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS: '-rabbitmq_mqtt tcp_listeners [{"0.0.0.0",1883}]' + volumes: + - ./rabbitmq/etc:/etc/rabbitmq/ + - rabbitmq-data:/var/lib/rabbitmq + command: > + bash -c "rabbitmq-plugins enable --offline rabbitmq_mqtt; + rabbitmq-server" + + tsdb: + container_name: tsdb + restart: no + build: + context: tsdb + dockerfile: Dockerfile + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres -d tracker_db"] + interval: 10s + timeout: 5s + retries: 5 + command: ["postgres", "-c", "shared_preload_libraries=timescaledb"] + environment: + POSTGRES_DB: tracker_db + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_INITDB_ARGS: "--auth-host=scram-sha-256 --auth-local=scram-sha-256" + volumes: + - tsdb-data:/var/lib/postgresql/data + - ./tsdb/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d:ro + + pgadmin: + image: dpage/pgadmin4:latest + container_name: pgadmin + restart: no + environment: + PGADMIN_DEFAULT_EMAIL: admin@admin.com + PGADMIN_DEFAULT_PASSWORD: admin + ports: + - "8085:80" + volumes: + - pgadmin-data:/var/lib/pgadmin + depends_on: + tsdb: + condition: service_healthy + + brokeroo: + container_name: brokeroo + restart: no + build: + context: brokeroo + dockerfile: Dockerfile + environment: + MQTT_BROKER: mqtt://rabbitmq:1883 + MQTT_CLIENT_ID: apps + MQTT_USERNAME: "apps" + MQTT_PASSWORD: "apps" + POSTGRES_URL: postgres://apps:apps@tsdb:5432/tracker_db?sslmode=disable + TOPIC_PATTERN: j/data/+/+ + KAFKA_BROKER: kafka:9092 + depends_on: + kafka: + condition: service_started + tsdb: + condition: service_healthy + rabbitmq: + condition: service_started + + grafana: + image: grafana/grafana:latest + container_name: grafana + restart: no + depends_on: + tsdb: + condition: service_healthy + environment: + GF_SECURITY_ADMIN_USER: admin + GF_SECURITY_ADMIN_PASSWORD: admin123 + GF_DATABASE_TYPE: postgres + GF_DATABASE_HOST: tsdb:5432 + GF_DATABASE_NAME: tracker_db + GF_DATABASE_USER: admin + GF_DATABASE_PASSWORD: administrator + ports: + - "3000:3000" + volumes: + - grafana-data:/var/lib/grafana + + zookeeper: + container_name: zookeeper + image: confluentinc/cp-zookeeper:7.5.0 + environment: + ZOOKEEPER_CLIENT_PORT: 2181 + ZOOKEEPER_TICK_TIME: 2000 + + kafka: + container_name: kafka + image: confluentinc/cp-kafka:7.5.0 + ports: + - "9092:9092" + environment: + KAFKA_BROKER_ID: 1 + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT + KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT + depends_on: + - zookeeper + + flink-jobmanager: + image: flink:1.20.0-scala_2.12 + platform: linux/amd64 + ports: + - "8081:8081" # dashboard web di Flink + command: jobmanager + environment: + - | + FLINK_PROPERTIES= + jobmanager.rpc.address: flink-jobmanager + depends_on: + - kafka + - tsdb + + flink-taskmanager: + image: flink:1.20.0-scala_2.12 + platform: linux/amd64 + command: taskmanager + scale: 1 + environment: + - | + FLINK_PROPERTIES= + jobmanager.rpc.address: flink-jobmanager + taskmanager.numberOfTaskSlots: 16 + depends_on: + - flink-jobmanager + + flink-job: + container_name: flink-job + build: + context: ./flink_job + dockerfile: Dockerfile + depends_on: + - flink-jobmanager + - flink-taskmanager + - kafka + - tsdb + environment: + - FLINK_JM_HOST=flink-jobmanager + - FLINK_JM_PORT=8081 + - FLINK_PARALLELISM=16 + - POSTGRES_URL=jdbc:postgresql://tsdb:5432/tracker_db?sslmode=disable + +volumes: + trackeroo-backend-data: + mongo-data: + rabbitmq-data: + tsdb-data: + pgadmin-data: + grafana-data: + redis-data: + +secrets: + users_key: + file: trackeroo-backend/secrets/users_key diff --git a/flink_job/.settings/org.eclipse.buildship.core.prefs b/flink_job/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 00000000..2eb415e8 --- /dev/null +++ b/flink_job/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,13 @@ +arguments=--init-script /home/leonardo/.local/share/zed/extensions/work/java/jdtls/jdt-language-server-1.51.0-202510022025/configuration/org.eclipse.osgi/58/0/.cp/gradle/init/init.gradle +auto.sync=false +build.scans.enabled=false +connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(8.9)) +connection.project.dir= +eclipse.preferences.version=1 +gradle.user.home= +java.home=/home/leonardo/android-studio/jbr +jvm.arguments= +offline.mode=false +override.workspace.settings=true +show.console.view=true +show.executions.view=true diff --git a/flink_job/Dockerfile b/flink_job/Dockerfile new file mode 100644 index 00000000..85724001 --- /dev/null +++ b/flink_job/Dockerfile @@ -0,0 +1,30 @@ +# Stage 1: build +FROM gradle:8.7-jdk11 AS builder +WORKDIR /app +COPY . . +RUN gradle shadowJar --no-daemon + +# Stage 2: runtime +FROM flink:1.20.0-scala_2.12 +WORKDIR /job +COPY --from=builder /app/build/libs/job-all.jar /job/job.jar + +# Script per attendere il JobManager e sottomettere con parallelismo +COPY <<'EOF' /job/start.sh +#!/bin/bash +set -e +echo "Waiting for Flink JobManager at ${FLINK_JM_HOST}:${FLINK_JM_PORT}..." +until curl -s http://${FLINK_JM_HOST}:${FLINK_JM_PORT}/overview; do + echo "JobManager not ready, retrying in 5s..." + sleep 5 +done +echo "JobManager ready! Submitting job with parallelism ${FLINK_PARALLELISM}" +exec /opt/flink/bin/flink run \ + -m ${FLINK_JM_HOST}:${FLINK_JM_PORT} \ + --parallelism ${FLINK_PARALLELISM} \ + -c com.example.job.AggregatorJob \ + /job/job.jar +EOF + +RUN chmod +x /job/start.sh +ENTRYPOINT ["/job/start.sh"] \ No newline at end of file diff --git a/flink_job/bin/main/com/example/job/AggregationHandler.class b/flink_job/bin/main/com/example/job/AggregationHandler.class new file mode 100644 index 00000000..a3d34df0 Binary files /dev/null and b/flink_job/bin/main/com/example/job/AggregationHandler.class differ diff --git a/flink_job/build.gradle b/flink_job/build.gradle new file mode 100644 index 00000000..fb830199 --- /dev/null +++ b/flink_job/build.gradle @@ -0,0 +1,43 @@ +plugins { + id 'java' + id 'application' + id 'com.github.johnrengelman.shadow' version '8.1.1' +} + +group = 'com.example' +version = '1.0-SNAPSHOT' +mainClassName = 'com.example.job.AggregatorJob' + +repositories { + mavenCentral() +} + +ext { + flinkVersion = '1.20.0' +} + +dependencies { + implementation "org.apache.flink:flink-streaming-java:${flinkVersion}" + implementation "org.apache.flink:flink-clients:${flinkVersion}" + + // VERSIONI REALI E VERIFICATE per Flink 1.20.0 + implementation "org.apache.flink:flink-connector-kafka:3.4.0-1.20" + implementation "org.apache.flink:flink-connector-jdbc:3.3.0-1.20" + + implementation "com.fasterxml.jackson.core:jackson-databind:2.15.2" + implementation "org.postgresql:postgresql:42.6.0" + implementation "org.slf4j:slf4j-simple:1.7.36" +} + +tasks.withType(JavaCompile) { + options.release = 11 +} + +shadowJar { + archiveBaseName.set('job') + archiveVersion.set('') + archiveClassifier.set('all') + manifest { + attributes 'Main-Class': mainClassName + } +} \ No newline at end of file diff --git a/flink_job/settings.gradle b/flink_job/settings.gradle new file mode 100644 index 00000000..9f41b789 --- /dev/null +++ b/flink_job/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'flink_job' \ No newline at end of file diff --git a/flink_job/src/main/java/com/example/job/AggregationHandler.java b/flink_job/src/main/java/com/example/job/AggregationHandler.java new file mode 100644 index 00000000..1b9ba177 --- /dev/null +++ b/flink_job/src/main/java/com/example/job/AggregationHandler.java @@ -0,0 +1,318 @@ +package com.example.job; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.Serializable; +import java.sql.Timestamp; +import java.security.MessageDigest; +import java.util.Map; + +public class AggregationHandler implements Serializable { + + private static final long serialVersionUID = 1L; + private transient ObjectMapper mapper; + + // Data classes + public static class Coordinate implements Serializable { + private static final long serialVersionUID = 1L; + public double lat; + public double lon; + } + + public static class Payload implements Serializable { + private static final long serialVersionUID = 1L; + public long ts; + public Double speed; + public Double speed_limit; + public Map speed_stats; + public Coordinate position; + public String device_name; + public String device_type; + public String device_id; + public String status; + public Object sensors; + public Coordinate start; + public Coordinate end; + public double instant_consumption; + public Map consumption_stats; + public double delta_distance; + } + + public static class ValuableSensors implements Serializable { + private static final long serialVersionUID = 1L; + public boolean alarm; + public Double vibration; + public boolean rear_hatch_open; + public boolean front_hatch_open; + public boolean collision; + } + + public static class FoodSensors implements Serializable { + private static final long serialVersionUID = 1L; + public boolean rear_hatch_open; + public boolean front_hatch_open; + public Double temperature; + public Double humidity; + public Double pressure; + } + + public static class Envelope implements Serializable { + private static final long serialVersionUID = 1L; + public long ts_unix; + public String ts; + public String dev_id; + public String tag; + public Payload payloadJson; + } + + public static class AggregatedRecord implements Serializable { + private static final long serialVersionUID = 1L; + public long ts_unix; + public Timestamp ts; + public String dev_id; + public String tag; + public String route_hash; + public String payloadJson; + } + + // Aggregation state holder + public static class AggregationState implements Serializable { + private static final long serialVersionUID = 1L; + public int count = 0; + public String route_hash = " "; + public String tag = " "; + public String status = " "; + public String device_name = " "; + public double delta_sum = 0.0; + public double speed_limit = 0.0; + public double sum_vibration = 0.0; + public double sum_humidity = 0.0; + public double sum_temperature = 0.0; + public double sum_pressure = 0.0; + public double avg_consumption = 0.0; + public double min_consumption = 0.0; + public double max_consumption = 0.0; + public double avg_speed = 0.0; + public double min_speed = 0.0; + public double max_speed = 0.0; + public boolean collision = false; + public boolean is_food = false; + public boolean is_valuable = false; + public boolean alarm = false; + public boolean is_pirate = false; + public Envelope last_element = null; + } + + public AggregationHandler() { + initMapper(); + } + + private void initMapper() { + if (mapper == null) { + mapper = new ObjectMapper(); + } + } + + public ObjectMapper getMapper() { + initMapper(); + return mapper; + } + + /** + * Hashes route coordinates to create a unique identifier + */ + public String hashCoordinates(Coordinate start, Coordinate end) { + try { + String input = start.lat + "," + start.lon + ";" + end.lat + "," + end.lon; + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(input.getBytes()); + + StringBuilder hexString = new StringBuilder(); + for (byte b : hash) { + hexString.append(String.format("%02x", b)); + } + + return hexString.toString().substring(0, 16); + } catch (Exception e) { + throw new RuntimeException("Error computing route hash", e); + } + } + + /** + * Processes all elements for a single device and builds aggregation state + */ + public AggregationState processElements(String key, Iterable elements) { + initMapper(); // Ensure mapper is initialized + AggregationState state = new AggregationState(); + + for (Envelope env : elements) { + state.last_element = env; + + if (env.payloadJson != null) { + + // Initialize on first element + if (state.count == 0) { + state.speed_limit = env.payloadJson.speed_limit; + state.device_name = env.payloadJson.device_name; + state.tag = env.tag; + state.route_hash = hashCoordinates(env.payloadJson.start, env.payloadJson.end); + } + + // Check for pirate behavior + if (env.payloadJson.speed > env.payloadJson.speed_limit + 10) { + state.is_pirate = true; + } + + state.count++; + state.delta_sum += env.payloadJson.delta_distance; + + // Handle device-specific sensors + if (env.payloadJson.device_type.equals("food") && env.payloadJson.sensors != null) { + state.is_food = true; + FoodSensors food_sensor = mapper.convertValue(env.payloadJson.sensors, FoodSensors.class); + + if (food_sensor.humidity != null) state.sum_humidity += food_sensor.humidity; + if (food_sensor.pressure != null) state.sum_pressure += food_sensor.pressure; + if (food_sensor.temperature != null) state.sum_temperature += food_sensor.temperature; + + System.out.printf(">>> [PARSED_FOOD OK] dev_type=%s%n", env.payloadJson.device_type); + + } else if (env.payloadJson.device_type.equals("valuable") && env.payloadJson.sensors != null) { + state.is_valuable = true; + ValuableSensors valuable_sensor = mapper.convertValue(env.payloadJson.sensors, ValuableSensors.class); + + if (valuable_sensor.collision) state.collision = true; + if (valuable_sensor.alarm) state.alarm = true; + if (valuable_sensor.vibration != null) state.sum_vibration += valuable_sensor.vibration; + + System.out.printf(">>> [PARSED_VALUABLE OK] dev_type=%s%n", env.payloadJson.device_type); + } + } + } + + // Extract final stats from last element + if (state.last_element != null) { + state.status = state.last_element.payloadJson.status; + Payload payload = state.last_element.payloadJson; + + state.avg_consumption = getDoubleValue(payload.consumption_stats, "avg"); + state.min_consumption = getDoubleValue(payload.consumption_stats, "min"); + state.max_consumption = getDoubleValue(payload.consumption_stats, "max"); + + state.avg_speed = getDoubleValue(payload.speed_stats, "avg"); + state.min_speed = getDoubleValue(payload.speed_stats, "min"); + state.max_speed = getDoubleValue(payload.speed_stats, "max"); + } + + System.out.printf(">>> [WINDOW] dev_id=%s, count=%d%n", key, state.count); + + return state; + } + + /** + * Builds the final aggregated record from the state + */ + public AggregatedRecord buildAggregatedRecord(String key, AggregationState state) { + if (state.count == 0) { + return null; + } + + long now = System.currentTimeMillis(); + + double avg_vibration = state.sum_vibration / state.count; + double avg_pressure = state.sum_pressure / state.count; + double avg_humidity = state.sum_humidity / state.count; + double avg_temperature = state.sum_temperature / state.count; + + AggregatedRecord record = new AggregatedRecord(); + record.ts_unix = now / 1000L; + record.ts = new Timestamp(now); + record.route_hash = state.route_hash; + record.dev_id = key; + record.tag = state.tag; + + if (state.is_food) { + record.payloadJson = buildFoodPayload(key, state, avg_humidity, avg_pressure, avg_temperature); + } else if (state.is_valuable) { + record.payloadJson = buildValuablePayload(key, state, avg_vibration); + } else { + record.payloadJson = buildGenericPayload(key, state); + } + + return record; + } + + private String buildFoodPayload(String key, AggregationState s, double avg_humidity, double avg_pressure, double avg_temperature) { + System.out.printf( + ">>> [AGGREGATED_FOOD] dev_id=%s avg_speed=%.2f count=%d delta_sum=%.2f speed_limit=%.2f " + + "avg_humidity=%.2f avg_pressure=%.2f avg_temperature=%.2f min_speed=%.2f max_speed=%.2f " + + "avg_consumption=%.2f min_consumption=%.2f max_consumption=%.2f status=%s device_name=%s is_pirate=%b%n", + key, s.avg_speed, s.count, s.delta_sum, s.speed_limit, + avg_humidity, avg_pressure, avg_temperature, + s.min_speed, s.max_speed, + s.avg_consumption, s.min_consumption, s.max_consumption, + s.status, s.device_name, s.is_pirate); + + return String.format( + "{\"avg_speed\": %.2f, \"count\": %d, \"delta_sum\": %.2f, \"speed_limit\": %.2f, " + + "\"avg_humidity\": %.2f, \"avg_pressure\": %.2f, \"avg_temperature\": %.2f, " + + "\"min_speed\": %.2f, \"max_speed\": %.2f, " + + "\"avg_consumption\": %.2f, \"min_consumption\": %.2f, \"max_consumption\": %.2f, " + + "\"status\": \"%s\", \"device_name\": \"%s\", \"is_pirate\": %b}", + s.avg_speed, s.count, s.delta_sum, s.speed_limit, + avg_humidity, avg_pressure, avg_temperature, + s.min_speed, s.max_speed, + s.avg_consumption, s.min_consumption, s.max_consumption, + s.status, s.device_name, s.is_pirate); + } + + private String buildValuablePayload(String key, AggregationState s, double avg_vibration) { + System.out.printf( + ">>> [AGGREGATED_VALUABLE] dev_id=%s avg_speed=%.2f count=%d delta_sum=%.2f speed_limit=%.2f " + + "collision=%b alarm=%b avg_vibration=%.2f min_speed=%.2f max_speed=%.2f " + + "avg_consumption=%.2f min_consumption=%.2f max_consumption=%.2f status=%s device_name=%s is_pirate=%b%n", + key, s.avg_speed, s.count, s.delta_sum, s.speed_limit, + s.collision, s.alarm, avg_vibration, + s.min_speed, s.max_speed, + s.avg_consumption, s.min_consumption, s.max_consumption, + s.status, s.device_name, s.is_pirate); + + return String.format( + "{\"avg_speed\": %.2f, \"count\": %d, \"delta_sum\": %.2f, \"speed_limit\": %.2f, " + + "\"collision\": %b, \"alarm\": %b, \"avg_vibration\": %.2f, " + + "\"min_speed\": %.2f, \"max_speed\": %.2f, " + + "\"avg_consumption\": %.2f, \"min_consumption\": %.2f, \"max_consumption\": %.2f, " + + "\"status\": \"%s\", \"device_name\": \"%s\", \"is_pirate\": %b}", + s.avg_speed, s.count, s.delta_sum, s.speed_limit, + s.collision, s.alarm, avg_vibration, + s.min_speed, s.max_speed, + s.avg_consumption, s.min_consumption, s.max_consumption, + s.status, s.device_name, s.is_pirate); + } + + private String buildGenericPayload(String key, AggregationState s) { + System.out.printf( + ">>> [AGGREGATED] dev_id=%s avg_speed=%.2f count=%d delta_sum=%.2f speed_limit=%.2f " + + "max_speed=%.2f min_speed=%.2f avg_consumption=%.2f min_consumption=%.2f max_consumption=%.2f status=%s device_name=%s is_pirate=%b%n", + key, s.avg_speed, s.count, s.delta_sum, s.speed_limit, + s.max_speed, s.min_speed, + s.avg_consumption, s.min_consumption, s.max_consumption, + s.status, s.device_name, s.is_pirate); + + return String.format( + "{\"avg_speed\": %.2f, \"count\": %d, \"delta_sum\": %.2f, \"speed_limit\": %.2f, " + + "\"max_speed\": %.2f, \"min_speed\": %.2f, " + + "\"avg_consumption\": %.2f, \"min_consumption\": %.2f, \"max_consumption\": %.2f, " + + "\"status\": \"%s\", \"device_name\": \"%s\", \"is_pirate\": %b}", + s.avg_speed, s.count, s.delta_sum, s.speed_limit, + s.max_speed, s.min_speed, + s.avg_consumption, s.min_consumption, s.max_consumption, + s.status, s.device_name, s.is_pirate); + } + + private double getDoubleValue(Map map, String key) { + if (map == null || map.get(key) == null) { + return 0.0; + } + return ((Number) map.get(key)).doubleValue(); + } +} diff --git a/flink_job/src/main/java/com/example/job/AggregatorJob.java b/flink_job/src/main/java/com/example/job/AggregatorJob.java new file mode 100644 index 00000000..0f3131b8 --- /dev/null +++ b/flink_job/src/main/java/com/example/job/AggregatorJob.java @@ -0,0 +1,214 @@ +package com.example.job; + +import com.example.job.AggregationHandler.AggregatedRecord; +import com.example.job.AggregationHandler.AggregationState; +import com.example.job.AggregationHandler.Envelope; +import java.io.Serializable; +import java.sql.Timestamp; +import java.time.Duration; +import java.time.Instant; +import java.util.regex.Pattern; +import org.apache.flink.api.common.eventtime.WatermarkStrategy; +import org.apache.flink.api.common.serialization.SimpleStringSchema; +import org.apache.flink.connector.jdbc.JdbcConnectionOptions; +import org.apache.flink.connector.jdbc.JdbcExecutionOptions; +import org.apache.flink.connector.jdbc.JdbcSink; +import org.apache.flink.connector.kafka.source.KafkaSource; +import org.apache.flink.connector.kafka.source.enumerator.initializer.OffsetsInitializer; +import org.apache.flink.connector.kafka.source.reader.deserializer.KafkaRecordDeserializationSchema; +import org.apache.flink.streaming.api.datastream.DataStream; +import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; +import org.apache.flink.streaming.api.functions.windowing.ProcessWindowFunction; +import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows; +import org.apache.flink.streaming.api.windowing.windows.TimeWindow; +import org.apache.flink.util.Collector; + +public class AggregatorJob { + + private static class AggregationProcessFunction + extends ProcessWindowFunction< + Envelope, + AggregatedRecord, + String, + TimeWindow + > + implements Serializable { + + private static final long serialVersionUID = 1L; + private final AggregationHandler handler; + + public AggregationProcessFunction(AggregationHandler handler) { + this.handler = handler; + } + + @Override + public void process( + String key, + Context context, + Iterable elements, + Collector out + ) { + AggregationState state = handler.processElements(key, elements); + + AggregatedRecord record = handler.buildAggregatedRecord(key, state); + + if (record != null) { + out.collect(record); + } + } + } + + public static void main(String[] args) throws Exception { + System.out.println(">>> [START] Flink Job at " + Instant.now()); + + final StreamExecutionEnvironment env = + StreamExecutionEnvironment.getExecutionEnvironment(); + final AggregationHandler handler = new AggregationHandler(); + + // Legge variabili d'ambiente (con default) + String kafkaUrl = System.getenv().getOrDefault( + "KAFKA_URL", + "kafka:9092" + ); + String postgresUrl = System.getenv().getOrDefault( + "POSTGRES_URL", + "jdbc:postgresql://tsdb:5432/tracker_db?sslmode=disable" + ); + + System.out.printf( + ">>> [DEBUG] Kafka: %s | Postgres: %s%n", + kafkaUrl, + postgresUrl + ); + + Pattern topicPattern = Pattern.compile("j-data-.*"); + + // Kafka Source configuration + KafkaSource kafkaSource = KafkaSource.builder() + .setBootstrapServers(kafkaUrl) + .setGroupId("flink-aggregator") + .setTopicPattern(topicPattern) + .setStartingOffsets(OffsetsInitializer.latest()) + .setDeserializer( + KafkaRecordDeserializationSchema.valueOnly( + new SimpleStringSchema() + ) + ) + .setProperty("partition.discovery.interval.ms", "5000") + .setProperty("fetch.max.wait.ms", "500") + .build(); + + // Raw stream from Kafka + DataStream rawStream = env + .fromSource( + kafkaSource, + WatermarkStrategy.forBoundedOutOfOrderness( + Duration.ofSeconds(5) + ), + "Kafka Source with Regex" + ) + .setParallelism(2); + + // Parse JSON messages + DataStream parsed = rawStream + .map(value -> { + System.out.printf( + ">>> [KAFKA_MSG] Received at %s: %s%n", + Instant.now(), + value + ); + try { + Envelope envObj = handler + .getMapper() + .readValue(value, Envelope.class); + System.out.printf( + ">>> [PARSED_OK] dev_id=%s tag=%s%n", + envObj.dev_id, + envObj.tag + ); + return envObj; + } catch (Exception e) { + System.err.printf( + ">>> [JSON_PARSE_ERROR] %s - Message: %s%n", + e.getMessage(), + value + ); + Envelope empty = new Envelope(); + empty.dev_id = "unknown"; + return empty; + } + }) + .setParallelism(4); + + // Filter valid messages + DataStream validParsed = parsed + .filter(e -> e.dev_id != null && !e.dev_id.equals("unknown")) + .setParallelism(2); + + // Aggregate by device_id in 10-second windows + DataStream aggregated = validParsed + .keyBy(e -> e.dev_id) + .window(TumblingProcessingTimeWindows.of(Duration.ofSeconds(10))) + .process(new AggregationProcessFunction(handler)) + .setParallelism(6); + + // Sink to PostgreSQL + aggregated + .addSink( + JdbcSink.sink( + "INSERT INTO trackeroo.aggregated (ts_unix, ts, dev_id, route_hash, insertion_time, tag, payload) " + + "VALUES (?, ?, ?, ?, ?, ?, ?) " + + "ON CONFLICT (route_hash, ts ,dev_id) DO UPDATE SET " + + "ts_unix = EXCLUDED.ts_unix, " + + "ts = EXCLUDED.ts, " + + "insertion_time = NOW(), " + + "payload = EXCLUDED.payload", + (ps, record) -> { + try { + ps.setLong(1, record.ts_unix); + ps.setTimestamp(2, record.ts); + ps.setString(3, record.dev_id); + ps.setString(4, record.route_hash); + ps.setTimestamp( + 5, + new Timestamp(System.currentTimeMillis()) + ); + ps.setString(6, record.tag); + ps.setObject( + 7, + record.payloadJson, + java.sql.Types.OTHER + ); + System.out.printf( + ">>> [DB_INSERT/UPSERT] dev_id=%s route=%s ok%n", + record.dev_id, + record.route_hash + ); + } catch (Exception e) { + System.err.printf( + ">>> [DB_ERROR] dev_id=%s route=%s | %s%n", + record.dev_id, + record.route_hash, + e.getMessage() + ); + throw e; + } + }, + JdbcExecutionOptions.builder() + .withBatchSize(1000) + .withBatchIntervalMs(200) + .withMaxRetries(5) + .build(), + new JdbcConnectionOptions.JdbcConnectionOptionsBuilder() + .withUrl(postgresUrl) + .withDriverName("org.postgresql.Driver") + .withUsername("apps") + .withPassword("apps") + .build() + ) + ) + .setParallelism(2); + + env.execute("Dynamic Kafka Aggregator Job (with minimal debug)"); + } +} diff --git a/flink_job/target/classes/com/example/job/AggregatorJob$AggregatedRecord.class b/flink_job/target/classes/com/example/job/AggregatorJob$AggregatedRecord.class new file mode 100644 index 00000000..b4cdf908 Binary files /dev/null and b/flink_job/target/classes/com/example/job/AggregatorJob$AggregatedRecord.class differ diff --git a/flink_job/target/classes/com/example/job/AggregatorJob$Coordinate.class b/flink_job/target/classes/com/example/job/AggregatorJob$Coordinate.class new file mode 100644 index 00000000..cd4d98a3 Binary files /dev/null and b/flink_job/target/classes/com/example/job/AggregatorJob$Coordinate.class differ diff --git a/flink_job/target/classes/com/example/job/AggregatorJob$Envelope.class b/flink_job/target/classes/com/example/job/AggregatorJob$Envelope.class new file mode 100644 index 00000000..e5140fe8 Binary files /dev/null and b/flink_job/target/classes/com/example/job/AggregatorJob$Envelope.class differ diff --git a/flink_job/target/classes/com/example/job/AggregatorJob$Payload.class b/flink_job/target/classes/com/example/job/AggregatorJob$Payload.class new file mode 100644 index 00000000..3716ec6d Binary files /dev/null and b/flink_job/target/classes/com/example/job/AggregatorJob$Payload.class differ diff --git a/flink_job/target/classes/com/example/job/AggregatorJob.class b/flink_job/target/classes/com/example/job/AggregatorJob.class new file mode 100644 index 00000000..a06ab29e Binary files /dev/null and b/flink_job/target/classes/com/example/job/AggregatorJob.class differ diff --git a/flink_job/version.yml b/flink_job/version.yml new file mode 100644 index 00000000..2ef3d523 --- /dev/null +++ b/flink_job/version.yml @@ -0,0 +1 @@ +version: 1.0.0 diff --git a/grafana-dashboards/grafana_test_dashboard.json b/grafana-dashboards/grafana_test_dashboard.json new file mode 100644 index 00000000..9f52a926 --- /dev/null +++ b/grafana-dashboards/grafana_test_dashboard.json @@ -0,0 +1,1387 @@ +{ + "__inputs": [ + { + "name": "DS_TRACKER_DB", + "label": "tracker_db", + "description": "", + "type": "datasource", + "pluginId": "grafana-postgresql-datasource", + "pluginName": "PostgreSQL" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "panel", + "id": "gauge", + "name": "Gauge", + "version": "" + }, + { + "type": "panel", + "id": "geomap", + "name": "Geomap", + "version": "" + }, + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "12.1.1" + }, + { + "type": "datasource", + "id": "grafana-postgresql-datasource", + "name": "PostgreSQL", + "version": "12.1.1" + }, + { + "type": "panel", + "id": "stat", + "name": "Stat", + "version": "" + }, + { + "type": "panel", + "id": "state-timeline", + "name": "State timeline", + "version": "" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "panels": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${DS_TRACKER_DB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 4, + "x": 0, + "y": 0 + }, + "id": 5, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${DS_TRACKER_DB}" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "WITH route_data AS (\n SELECT \n (payload->'start'->>'lat')::float AS start_lat,\n (payload->'start'->>'lon')::float AS start_lon,\n (payload->'end'->>'lat')::float AS end_lat,\n (payload->'end'->>'lon')::float AS end_lon,\n (payload->'position'->>'lat')::float AS current_lat,\n (payload->'position'->>'lon')::float AS current_lon\n FROM trackeroo.data\n WHERE dev_id = '$dev_id'\n AND payload->'start'->>'lat' IS NOT NULL\n AND payload->'end'->>'lat' IS NOT NULL\n AND payload->'position'->>'lat' IS NOT NULL\n ORDER BY ts DESC\n LIMIT 1\n)\nSELECT \n CASE \n WHEN ST_Distance(\n ST_GeogFromText('POINT(' || start_lon || ' ' || start_lat || ')'),\n ST_GeogFromText('POINT(' || end_lon || ' ' || end_lat || ')')\n ) > 0 THEN\n ROUND((ST_Distance(\n ST_GeogFromText('POINT(' || start_lon || ' ' || start_lat || ')'),\n ST_GeogFromText('POINT(' || current_lon || ' ' || current_lat || ')')\n ) / ST_Distance(\n ST_GeogFromText('POINT(' || start_lon || ' ' || start_lat || ')'),\n ST_GeogFromText('POINT(' || end_lon || ' ' || end_lat || ')')\n ) * 100)::numeric, 2)\n ELSE 0\n END AS \"Progress\"\nFROM route_data;", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Progress", + "type": "stat" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${DS_TRACKER_DB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "m" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 4, + "x": 4, + "y": 0 + }, + "id": 6, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${DS_TRACKER_DB}" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "WITH route_data AS (\n SELECT \n (payload->'start'->>'lat')::float AS start_lat,\n (payload->'start'->>'lon')::float AS start_lon,\n (payload->'end'->>'lat')::float AS end_lat,\n (payload->'end'->>'lon')::float AS end_lon,\n (payload->'position'->>'lat')::float AS current_lat,\n (payload->'position'->>'lon')::float AS current_lon\n FROM trackeroo.data\n WHERE dev_id = '$dev_id'\n AND payload->'start'->>'lat' IS NOT NULL\n AND payload->'end'->>'lat' IS NOT NULL\n AND payload->'position'->>'lat' IS NOT NULL\n ORDER BY ts DESC\n LIMIT 1\n),\navg_speed AS (\n SELECT \n AVG(COALESCE((payload->'speed')::float, 0)) AS speed_kmh\n FROM trackeroo.data\n WHERE dev_id = '$dev_id'\n AND payload->'speed' IS NOT NULL\n AND (payload->'speed')::float > 0\n AND ts >= NOW() - INTERVAL '30 minutes'\n)\nSELECT \n CASE \n WHEN s.speed_kmh > 0 THEN\n ROUND((ST_Distance(\n ST_GeogFromText('POINT(' || rd.current_lon || ' ' || rd.current_lat || ')'),\n ST_GeogFromText('POINT(' || rd.end_lon || ' ' || rd.end_lat || ')')\n ) / 1000.0 / s.speed_kmh * 60)::numeric, 0)\n ELSE NULL\n END AS \"ETA\"\nFROM route_data rd\nCROSS JOIN avg_speed s;", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "ETA", + "type": "stat" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${DS_TRACKER_DB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 5, + "x": 8, + "y": 0 + }, + "id": 7, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT \n\npayload->'device_name' \nAS \"Device Name\"\nFROM trackeroo.data \nWHERE\n (payload->'speed_stats'->>'avg')::float > (payload->'speed_limit')::float*1.3\n\nGROUP BY payload->'device_name'", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${DS_TRACKER_DB}" + } + } + ], + "title": "Pirates", + "type": "table" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${DS_TRACKER_DB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 15, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 2, + "options": { + "basemap": { + "config": {}, + "name": "Layer 0", + "type": "default" + }, + "controls": { + "mouseWheelZoom": true, + "showAttribution": true, + "showDebug": false, + "showMeasure": true, + "showScale": true, + "showZoom": true + }, + "layers": [ + { + "config": { + "showLegend": true, + "style": { + "color": { + "fixed": "light-green" + }, + "opacity": 0.2, + "rotation": { + "fixed": 0, + "max": 360, + "min": -360, + "mode": "mod" + }, + "size": { + "fixed": 3, + "max": 15, + "min": 2 + }, + "symbol": { + "fixed": "img/icons/marker/circle.svg", + "mode": "fixed" + }, + "symbolAlign": { + "horizontal": "center", + "vertical": "center" + }, + "textConfig": { + "fontSize": 12, + "offsetX": 0, + "offsetY": 0, + "textAlign": "center", + "textBaseline": "middle" + } + } + }, + "filterData": { + "id": "byRefId", + "options": "A" + }, + "location": { + "mode": "auto" + }, + "name": "Trace", + "tooltip": true, + "type": "markers" + }, + { + "config": { + "showLegend": true, + "style": { + "color": { + "fixed": "blue" + }, + "opacity": 1, + "rotation": { + "fixed": 0, + "max": 360, + "min": -360, + "mode": "mod" + }, + "size": { + "fixed": 5, + "max": 15, + "min": 2 + }, + "symbol": { + "fixed": "img/icons/marker/circle.svg", + "mode": "fixed" + }, + "symbolAlign": { + "horizontal": "center", + "vertical": "center" + }, + "text": { + "field": "Dev Name", + "fixed": "", + "mode": "field" + }, + "textConfig": { + "fontSize": 8, + "offsetX": 0, + "offsetY": -4, + "textAlign": "center", + "textBaseline": "bottom" + } + } + }, + "filterData": { + "id": "byRefId", + "options": "B" + }, + "location": { + "mode": "auto" + }, + "name": "Curr pos", + "tooltip": true, + "type": "markers" + }, + { + "config": { + "showLegend": true, + "style": { + "color": { + "fixed": "green" + }, + "opacity": 1, + "rotation": { + "fixed": 0, + "max": 360, + "min": -360, + "mode": "mod" + }, + "size": { + "fixed": 8, + "max": 15, + "min": 2 + }, + "symbol": { + "fixed": "img/icons/marker/x-mark.svg", + "mode": "fixed" + }, + "symbolAlign": { + "horizontal": "center", + "vertical": "center" + }, + "textConfig": { + "fontSize": 12, + "offsetX": 0, + "offsetY": 0, + "textAlign": "center", + "textBaseline": "middle" + } + } + }, + "filterData": { + "id": "byRefId", + "options": "C" + }, + "location": { + "mode": "auto" + }, + "name": "Start", + "tooltip": true, + "type": "markers" + }, + { + "config": { + "showLegend": true, + "style": { + "color": { + "fixed": "orange" + }, + "opacity": 1, + "rotation": { + "fixed": 0, + "max": 360, + "min": -360, + "mode": "mod" + }, + "size": { + "fixed": 9, + "max": 15, + "min": 2 + }, + "symbol": { + "fixed": "img/icons/marker/x-mark.svg", + "mode": "fixed" + }, + "symbolAlign": { + "horizontal": "center", + "vertical": "center" + }, + "textConfig": { + "fontSize": 12, + "offsetX": 0, + "offsetY": 0, + "textAlign": "center", + "textBaseline": "middle" + } + } + }, + "filterData": { + "id": "byRefId", + "options": "D" + }, + "location": { + "mode": "auto" + }, + "name": "End", + "tooltip": true, + "type": "markers" + } + ], + "tooltip": { + "mode": "details" + }, + "view": { + "allLayers": true, + "id": "coords", + "lat": 43.538469, + "lon": 11.382836, + "zoom": 8.12 + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${DS_TRACKER_DB}" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT \nts,\npayload->'position'->>'lat' as lat,\npayload->'position'->>'lon' as lon\nFROM trackeroo.data\nWHERE $__timeFilter(ts)\nand dev_id = '$dev_id'\nORDER BY ts DESC", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${DS_TRACKER_DB}" + }, + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "SELECT \nts,\npayload->'position'->>'lat' as lat,\npayload->'position'->>'lon' as lon,\npayload->'device_name' as \"Dev Name\"\nFROM trackeroo.data\nWHERE $__timeFilter(ts)\nand dev_id = '$dev_id'\nORDER BY ts desc\nLIMIT 1", + "refId": "B", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${DS_TRACKER_DB}" + }, + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "SELECT \nts,\npayload->'start'->>'lat' as lat,\npayload->'start'->>'lon' as lon\nFROM trackeroo.data\nWHERE dev_id = '$dev_id'\nORDER BY ts DESC\nlimit 1", + "refId": "C", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${DS_TRACKER_DB}" + }, + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "SELECT \nts,\npayload->'end'->>'lat' as lat,\npayload->'end'->>'lon' as lon\nFROM trackeroo.data\nWHERE dev_id = '$dev_id'\nORDER BY ts DESC\nlimit 1", + "refId": "D", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Map", + "type": "geomap" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${DS_TRACKER_DB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 15, + "w": 24, + "x": 0, + "y": 24 + }, + "id": 8, + "options": { + "basemap": { + "config": {}, + "name": "Layer 0", + "type": "default" + }, + "controls": { + "mouseWheelZoom": true, + "showAttribution": true, + "showDebug": false, + "showMeasure": true, + "showScale": true, + "showZoom": true + }, + "layers": [ + { + "config": { + "showLegend": true, + "style": { + "color": { + "fixed": "blue" + }, + "opacity": 1, + "rotation": { + "fixed": 0, + "max": 360, + "min": -360, + "mode": "mod" + }, + "size": { + "fixed": 5, + "max": 15, + "min": 2 + }, + "symbol": { + "fixed": "img/icons/marker/circle.svg", + "mode": "fixed" + }, + "symbolAlign": { + "horizontal": "center", + "vertical": "center" + }, + "text": { + "field": "Dev Name", + "fixed": "", + "mode": "field" + }, + "textConfig": { + "fontSize": 8, + "offsetX": 0, + "offsetY": -4, + "textAlign": "center", + "textBaseline": "bottom" + } + } + }, + "filterData": { + "id": "byRefId", + "options": "A" + }, + "location": { + "mode": "auto" + }, + "name": "Curr pos", + "tooltip": true, + "type": "markers" + } + ], + "tooltip": { + "mode": "details" + }, + "view": { + "allLayers": true, + "id": "coords", + "lat": 43.538469, + "lon": 11.382836, + "zoom": 8.12 + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${DS_TRACKER_DB}" + }, + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "SELECT DISTINCT ON (dev_id)\n ts,\n payload->'position'->>'lat' as lat,\n payload->'position'->>'lon' as lon,\n payload->'device_name' as \"Dev Name\",\n dev_id\nFROM trackeroo.data\nWHERE $__timeFilter(ts)\nORDER BY dev_id, ts DESC;", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Map", + "type": "geomap" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${DS_TRACKER_DB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Speed limit" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + }, + { + "id": "unit", + "value": "velocitykmh" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Max consumption" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "semi-dark-orange", + "mode": "fixed" + } + }, + { + "id": "unit", + "value": "" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Avg consumption" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-orange", + "mode": "fixed" + } + }, + { + "id": "unit" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Avg speed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + }, + { + "id": "unit", + "value": "velocitykmh" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Max speed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "semi-dark-blue", + "mode": "fixed" + } + }, + { + "id": "unit", + "value": "velocitykmh" + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 39 + }, + "id": 1, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${DS_TRACKER_DB}" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT \nts as Time,\n(payload->'speed_stats'->>'avg')::float as \"Avg speed\",\n(payload->'speed_stats'->>'max')::float as \"Max speed\",\n(payload->'speed_limit')::float as \"Speed limit\",\n(payload->'consumption_stats'->>'avg')::float as \"Avg consumption\",\n(payload->'consumption_stats'->>'max')::float as \"Max consumption\"\nFROM trackeroo.data \n-- WHERE dev_id = 'trk-185ed4ee37d05c72bf5fa09b'\nWHERE $__timeFilter(ts) \nand dev_id = '$dev_id'\nORDER BY ts DESC\nLIMIT 1", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Last data", + "type": "gauge" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${DS_TRACKER_DB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [ + { + "options": { + "\"accelerating\"": { + "color": "blue", + "index": 3, + "text": "A" + }, + "\"driving\"": { + "color": "green", + "index": 0, + "text": "D" + }, + "\"slowing\"": { + "color": "yellow", + "index": 2, + "text": "SL" + }, + "\"stopped\"": { + "color": "red", + "index": 1, + "text": "ST" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 24, + "x": 0, + "y": 45 + }, + "id": 3, + "options": { + "alignValue": "left", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${DS_TRACKER_DB}" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT ts as Time,\n\n payload->'status' as Status\n FROM trackeroo.data \nWHERE $__timeFilter(ts) \nand dev_id = '$dev_id'\nORDER BY ts DESC ", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Vehicle status", + "type": "state-timeline" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${DS_TRACKER_DB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Speed limit" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Max consumption" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "semi-dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Avg consumption" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Avg speed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Max speed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "semi-dark-blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 16, + "w": 24, + "x": 0, + "y": 49 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${DS_TRACKER_DB}" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT \nts as Time,\n(payload->'speed_stats'->>'avg')::float as \"Avg speed\",\n(payload->'speed_stats'->>'max')::float as \"Max speed\",\n(payload->'speed_limit')::float as \"Speed limit\",\n(payload->'consumption_stats'->>'avg')::float as \"Avg consumption\",\n(payload->'consumption_stats'->>'max')::float as \"Max consumption\"\nFROM trackeroo.data \n-- WHERE dev_id = 'trk-185ed4ee37d05c72bf5fa09b'\nWHERE $__timeFilter(ts) \nand dev_id = '$dev_id'\nORDER BY ts DESC", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "New panel", + "type": "timeseries" + } + ], + "refresh": "30s", + "schemaVersion": 41, + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "${DS_TRACKER_DB}" + }, + "definition": "SELECT * FROM ( VALUES\n('admiring_bohr', 'trk-18608d12c8446943ffbf2df9'),\n('adoring_morse', 'trk-18608d12c84d9ce75be19956'),\n('agitated_planck', 'trk-18608d12c845c8050d2973c1'),\n('amazing_turing', 'trk-18608d12c84770f5153b97ef'),\n('boring_heisenberg', 'trk-18608d12c84b469627a640d9'),\n('boring_wozniak', 'trk-18608d12c84c5151bb792931'),\n('brave_schrodinger', 'trk-18608d12c844b01d0d668fce'),\n('charming_dirac', 'trk-18608d12c844fccd26144714'),\n('clever_fermi', 'trk-18608d12c8449838a8eb28eb'),\n('clever_newton', 'trk-18608d12c846ca7bab489e30'),\n('competent_rutherford', 'trk-18608d12c848eb34f8760e19'),\n('condescending_volta', 'trk-18608d12c84d62f4adb3c923'),\n('cool_ohm', 'trk-18608d12c84ba2911c4a675e'),\n('cranky_ampere', 'trk-18608d12c84742df27b88f6b'),\n('curious_feynman', 'trk-18608d12c84c678173241105'),\n('dazzling_maxwell', 'trk-18608d12c846e0a3e38974e5'),\n('determined_kelvin', 'trk-18608d12c845df659619c7e6'),\n('distracted_planck', 'trk-18608d12c8498d777941e066'),\n('dreamy_tesla', 'trk-18608d12c84cdbbc4c9327d2'),\n('eager_darwin', 'trk-18608d12c84aa049b09a2169'),\n('eager_gauss', 'trk-18608d12c84a892c3aad3955'),\n('ecstatic_riemann', 'trk-18608d12c8472b9f0905b3ad'),\n('elastic_fourier', 'trk-18608d12c84db5aa45ada38e'),\n('elegant_lagrange', 'trk-18608d12c847064ce1bdf7ee'),\n('eloquent_leibniz', 'trk-18608d12c84b8b83746dbc8b'),\n('enchanting_poincare', 'trk-18608d12c84acd0d1c7fc74a'),\n('energetic_hilbert', 'trk-18608d12c84759c8a9ef04f8'),\n('epic_cantor', 'trk-18608d12c84823aa3e06b7da'),\n('exciting_godel', 'trk-18608d12c84e12b161fb52f1'),\n('exotic_turing', 'trk-18608d12c84eaef489342f7e'),\n('fabulous_ramanujan', 'trk-18608d12c84e473a6d897698'),\n('faithful_hardy', 'trk-18608d12c84b74702fe3e85c'),\n('fascinated_noether', 'trk-18608d12c848b620cb14aa25'),\n('fearless_galois', 'trk-18608d12c84976fefd57d1ff'),\n('fervent_abel', 'trk-18608d12c84bde9e8b791f18'),\n('friendly_curie', 'trk-18608d12c844e58e640b9ea1'),\n('frosty_dedekind', 'trk-18608d12c8486259917b5535'),\n('funny_peano', 'trk-18608d12c8479d828433ff9a'),\n('gallant_whitehead', 'trk-18608d12c84685251cdaf35c'),\n('gentle_church', 'trk-18608d12c8456a397013ce6b'),\n('gifted_kleene', 'trk-18608d12c84ec5eea3a8dae1'),\n('goofy_markov', 'trk-18608d12c84de3a8df5efbcc'),\n('great_kolmogorov', 'trk-18608d12c84d0b5fc332f041'),\n('grieving_wiener', 'trk-18608d12c84c23101fa0a8b6'),\n('groovy_shannon', 'trk-18608d12c84bf63dcc1fb3e7'),\n('happy_einstein', 'trk-18608d12c84786f21e4edc5e'),\n('hardcore_bell', 'trk-18608d12c84ab6d05b8082ad'),\n('heartwarming_bose', 'trk-18608d12c8487a58b5c5651d'),\n('heuristic_fermi', 'trk-18608d12c84640a54b9bd528'),\n('hopeful_bardeen', 'trk-18608d12c8483aeb4dce2724'),\n('hyper_shockley', 'trk-18608d12c8460bf33167dbdc'),\n('inspiring_bardeen', 'trk-18608d12c84afa2c10aa6c55'),\n('interesting_watson', 'trk-18608d12c84d2247a477b6ff'),\n('iron_franklin', 'trk-18608d12c8451452004ec96d'),\n('jaunty_pauling', 'trk-18608d12c849d409351613c2'),\n('jovial_mendeleev', 'trk-18608d12c84cf3df4741680f'),\n('keen_bohr', 'trk-18608d12c84480bd0ad4656f'),\n('kind_torvalds', 'trk-18608d12c848919ae0a5ac8f'),\n('laughing_rutherford', 'trk-18608d12c8469c00840cc49b'),\n('lucid_heisenberg', 'trk-18608d12c84ee5a2e49c6f83'),\n('magical_dirac', 'trk-18608d12c8491a91b1f888a9'),\n('merry_schwinger', 'trk-18608d12c8443834a667d6c2'),\n('modest_dyson', 'trk-18608d12c84b2feca3792861'),\n('nervous_hawking', 'trk-18608d12c84e95effa9dc925'),\n('noble_weinberg', 'trk-18608d12c84c7e6385d059d6'),\n('objective_glashow', 'trk-18608d12c84b11c367999861'),\n('optimistic_babbage', 'trk-18608d12c849a50c845c25a9'),\n('optimized_higgs', 'trk-18608d12c84c3ae0fd8aad7e'),\n('patient_wu', 'trk-18608d12c8480b18fe88b4af'),\n('peaceful_pascal', 'trk-18608d12c8494897112ba78e'),\n('pedantic_pauli', 'trk-18608d12c8490334a6303366'),\n('phenomenal_born', 'trk-18608d12c845ad105b9a60db'),\n('pious_planck', 'trk-18608d12c8462309d5e746cc'),\n('practical_michelson', 'trk-18608d12c84581a0b80ca1c5'),\n('proud_morley', 'trk-18608d12c84ae3dffff2e52d'),\n('puzzled_fizeau', 'trk-18608d12c848d48480a5d5b6'),\n('quirky_shannon', 'trk-18608d12c84e7f725b9dd529'),\n('quizzical_doppler', 'trk-18608d12c84caba1375500b4'),\n('relaxed_turing', 'trk-18608d12c84dfa6590d8fbda'),\n('romantic_hertz', 'trk-18608d12c8466e529b599dc4'),\n('sad_marconi', 'trk-18608d12c846b3600123a417'),\n('serene_hopper', 'trk-18608d12c84a5f766256c9e2'),\n('serene_tesla', 'trk-18608d12c84931d6fa2d6232'),\n('sharp_edison', 'trk-18608d12c846577d4ec97cfb'),\n('silly_westinghouse', 'trk-18608d12c84cc3de436f7bde'),\n('stoic_ohm', 'trk-18608d12c8495fd71857ce3f'),\n('strange_ampere', 'trk-18608d12c84b5cae4cff3133'),\n('suspicious_volta', 'trk-18608d12c84a2e54a6ffdd76'),\n('sweet_galvani', 'trk-18608d12c84e2a4a52da79d9'),\n('tender_faraday', 'trk-18608d12c849bd6d485879c1'),\n('thirsty_henry', 'trk-18608d12c8445101b3d3a3b8'),\n('thoughtful_weber', 'trk-18608d12c845f5d103e908ad'),\n('thrilled_gauss', 'trk-18608d12c84dccaa3ac843bd'),\n('trusting_knuth', 'trk-18608d12c84e67b817e2a8fd'),\n('upbeat_ritchie', 'trk-18608d12c84c9470140d32f6'),\n('vibrant_thompson', 'trk-18608d12c847b3e74258eb03'),\n('wonderful_wirth', 'trk-18608d12c8414acfcb9165b1'),\n('xenodochial_carmack', 'trk-18608d12c843cfc86452c8bc'),\n('youthful_stallman', 'trk-18608d12c844cb262515236b'),\n('zealous_berners', 'trk-18608d12c84d385e1d5c7bbc')\n) AS t (__text, __value)", + "name": "dev_id", + "options": [], + "query": "SELECT * FROM ( VALUES\n('admiring_bohr', 'trk-18608d12c8446943ffbf2df9'),\n('adoring_morse', 'trk-18608d12c84d9ce75be19956'),\n('agitated_planck', 'trk-18608d12c845c8050d2973c1'),\n('amazing_turing', 'trk-18608d12c84770f5153b97ef'),\n('boring_heisenberg', 'trk-18608d12c84b469627a640d9'),\n('boring_wozniak', 'trk-18608d12c84c5151bb792931'),\n('brave_schrodinger', 'trk-18608d12c844b01d0d668fce'),\n('charming_dirac', 'trk-18608d12c844fccd26144714'),\n('clever_fermi', 'trk-18608d12c8449838a8eb28eb'),\n('clever_newton', 'trk-18608d12c846ca7bab489e30'),\n('competent_rutherford', 'trk-18608d12c848eb34f8760e19'),\n('condescending_volta', 'trk-18608d12c84d62f4adb3c923'),\n('cool_ohm', 'trk-18608d12c84ba2911c4a675e'),\n('cranky_ampere', 'trk-18608d12c84742df27b88f6b'),\n('curious_feynman', 'trk-18608d12c84c678173241105'),\n('dazzling_maxwell', 'trk-18608d12c846e0a3e38974e5'),\n('determined_kelvin', 'trk-18608d12c845df659619c7e6'),\n('distracted_planck', 'trk-18608d12c8498d777941e066'),\n('dreamy_tesla', 'trk-18608d12c84cdbbc4c9327d2'),\n('eager_darwin', 'trk-18608d12c84aa049b09a2169'),\n('eager_gauss', 'trk-18608d12c84a892c3aad3955'),\n('ecstatic_riemann', 'trk-18608d12c8472b9f0905b3ad'),\n('elastic_fourier', 'trk-18608d12c84db5aa45ada38e'),\n('elegant_lagrange', 'trk-18608d12c847064ce1bdf7ee'),\n('eloquent_leibniz', 'trk-18608d12c84b8b83746dbc8b'),\n('enchanting_poincare', 'trk-18608d12c84acd0d1c7fc74a'),\n('energetic_hilbert', 'trk-18608d12c84759c8a9ef04f8'),\n('epic_cantor', 'trk-18608d12c84823aa3e06b7da'),\n('exciting_godel', 'trk-18608d12c84e12b161fb52f1'),\n('exotic_turing', 'trk-18608d12c84eaef489342f7e'),\n('fabulous_ramanujan', 'trk-18608d12c84e473a6d897698'),\n('faithful_hardy', 'trk-18608d12c84b74702fe3e85c'),\n('fascinated_noether', 'trk-18608d12c848b620cb14aa25'),\n('fearless_galois', 'trk-18608d12c84976fefd57d1ff'),\n('fervent_abel', 'trk-18608d12c84bde9e8b791f18'),\n('friendly_curie', 'trk-18608d12c844e58e640b9ea1'),\n('frosty_dedekind', 'trk-18608d12c8486259917b5535'),\n('funny_peano', 'trk-18608d12c8479d828433ff9a'),\n('gallant_whitehead', 'trk-18608d12c84685251cdaf35c'),\n('gentle_church', 'trk-18608d12c8456a397013ce6b'),\n('gifted_kleene', 'trk-18608d12c84ec5eea3a8dae1'),\n('goofy_markov', 'trk-18608d12c84de3a8df5efbcc'),\n('great_kolmogorov', 'trk-18608d12c84d0b5fc332f041'),\n('grieving_wiener', 'trk-18608d12c84c23101fa0a8b6'),\n('groovy_shannon', 'trk-18608d12c84bf63dcc1fb3e7'),\n('happy_einstein', 'trk-18608d12c84786f21e4edc5e'),\n('hardcore_bell', 'trk-18608d12c84ab6d05b8082ad'),\n('heartwarming_bose', 'trk-18608d12c8487a58b5c5651d'),\n('heuristic_fermi', 'trk-18608d12c84640a54b9bd528'),\n('hopeful_bardeen', 'trk-18608d12c8483aeb4dce2724'),\n('hyper_shockley', 'trk-18608d12c8460bf33167dbdc'),\n('inspiring_bardeen', 'trk-18608d12c84afa2c10aa6c55'),\n('interesting_watson', 'trk-18608d12c84d2247a477b6ff'),\n('iron_franklin', 'trk-18608d12c8451452004ec96d'),\n('jaunty_pauling', 'trk-18608d12c849d409351613c2'),\n('jovial_mendeleev', 'trk-18608d12c84cf3df4741680f'),\n('keen_bohr', 'trk-18608d12c84480bd0ad4656f'),\n('kind_torvalds', 'trk-18608d12c848919ae0a5ac8f'),\n('laughing_rutherford', 'trk-18608d12c8469c00840cc49b'),\n('lucid_heisenberg', 'trk-18608d12c84ee5a2e49c6f83'),\n('magical_dirac', 'trk-18608d12c8491a91b1f888a9'),\n('merry_schwinger', 'trk-18608d12c8443834a667d6c2'),\n('modest_dyson', 'trk-18608d12c84b2feca3792861'),\n('nervous_hawking', 'trk-18608d12c84e95effa9dc925'),\n('noble_weinberg', 'trk-18608d12c84c7e6385d059d6'),\n('objective_glashow', 'trk-18608d12c84b11c367999861'),\n('optimistic_babbage', 'trk-18608d12c849a50c845c25a9'),\n('optimized_higgs', 'trk-18608d12c84c3ae0fd8aad7e'),\n('patient_wu', 'trk-18608d12c8480b18fe88b4af'),\n('peaceful_pascal', 'trk-18608d12c8494897112ba78e'),\n('pedantic_pauli', 'trk-18608d12c8490334a6303366'),\n('phenomenal_born', 'trk-18608d12c845ad105b9a60db'),\n('pious_planck', 'trk-18608d12c8462309d5e746cc'),\n('practical_michelson', 'trk-18608d12c84581a0b80ca1c5'),\n('proud_morley', 'trk-18608d12c84ae3dffff2e52d'),\n('puzzled_fizeau', 'trk-18608d12c848d48480a5d5b6'),\n('quirky_shannon', 'trk-18608d12c84e7f725b9dd529'),\n('quizzical_doppler', 'trk-18608d12c84caba1375500b4'),\n('relaxed_turing', 'trk-18608d12c84dfa6590d8fbda'),\n('romantic_hertz', 'trk-18608d12c8466e529b599dc4'),\n('sad_marconi', 'trk-18608d12c846b3600123a417'),\n('serene_hopper', 'trk-18608d12c84a5f766256c9e2'),\n('serene_tesla', 'trk-18608d12c84931d6fa2d6232'),\n('sharp_edison', 'trk-18608d12c846577d4ec97cfb'),\n('silly_westinghouse', 'trk-18608d12c84cc3de436f7bde'),\n('stoic_ohm', 'trk-18608d12c8495fd71857ce3f'),\n('strange_ampere', 'trk-18608d12c84b5cae4cff3133'),\n('suspicious_volta', 'trk-18608d12c84a2e54a6ffdd76'),\n('sweet_galvani', 'trk-18608d12c84e2a4a52da79d9'),\n('tender_faraday', 'trk-18608d12c849bd6d485879c1'),\n('thirsty_henry', 'trk-18608d12c8445101b3d3a3b8'),\n('thoughtful_weber', 'trk-18608d12c845f5d103e908ad'),\n('thrilled_gauss', 'trk-18608d12c84dccaa3ac843bd'),\n('trusting_knuth', 'trk-18608d12c84e67b817e2a8fd'),\n('upbeat_ritchie', 'trk-18608d12c84c9470140d32f6'),\n('vibrant_thompson', 'trk-18608d12c847b3e74258eb03'),\n('wonderful_wirth', 'trk-18608d12c8414acfcb9165b1'),\n('xenodochial_carmack', 'trk-18608d12c843cfc86452c8bc'),\n('youthful_stallman', 'trk-18608d12c844cb262515236b'),\n('zealous_berners', 'trk-18608d12c84d385e1d5c7bbc')\n) AS t (__text, __value)", + "refresh": 1, + "regex": "", + "type": "query" + } + ] + }, + "time": { + "from": "now-30m", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Test", + "uid": "17b3218b-4ebe-43cd-876b-476db6af2838", + "version": 12, + "weekStart": "" +} \ No newline at end of file diff --git a/images/tsdb/docker-entrypoint-initdb.d/extensions.sql b/images/tsdb/docker-entrypoint-initdb.d/extensions.sql deleted file mode 100644 index 48433da9..00000000 --- a/images/tsdb/docker-entrypoint-initdb.d/extensions.sql +++ /dev/null @@ -1,3 +0,0 @@ -CREATE EXTENSION IF NOT EXISTS timescaledb; -CREATE EXTENSION IF NOT EXISTS postgis; -CREATE EXTENSION IF NOT EXISTS postgis_topology; diff --git a/mongo/init.js b/mongo/init.js new file mode 100644 index 00000000..c084bb01 --- /dev/null +++ b/mongo/init.js @@ -0,0 +1,850 @@ +db = db.getSiblingDB("trackeroo-backend"); +db.users.insertMany([ + { + username: "leonardo", + role: "admin", + password_hash: "$2b$12$PHQyHC3QX7hSaSNk3otnJe90Htsf5nIqNeqSMKCWrmCHwbDvEUrwm", + last_login: new Date(), + session_token: "", + issued_at: new Date(), + expires_at: new Date(Date.now() + 24*60*60*1000) // 24 hours from now + }, + { + username: "simone", + role: "admin", + password_hash: "$2b$12$CCBM/Xy54gPzHkJyMWovm.fTmUUqjg73GWb1cBDTKPaMU4JsfSXd6", + last_login: new Date(), + session_token: "", + issued_at: new Date(), + expires_at: new Date(Date.now() + 24*60*60*1000) + }, + { + username: "admin", + role: "admin", + password_hash: "$2b$12$3CaXyt.SvoUeQiR5TAFdReV4AjnAlNe46/oL6SBd75souC5SvKLAu", + last_login: new Date(), + session_token: "", + issued_at: new Date(), + expires_at: new Date(Date.now() + 24*60*60*1000) + }, + { + username: "apps", + role: "admin", + password_hash: "$2b$12$ZhKtxqqkFfrcEXiKjgvM0.BqM9sVXTEZAQCP5/qhRxMBZn2cWWbJ6", + last_login: new Date(), + session_token: "", + issued_at: new Date(), + expires_at: new Date(Date.now() + 24*60*60*1000) + }, + { + username: "trackeroo", + role: "admin", + password_hash: "$2b$12$NAfj6rvgE20aWfxfK9Y0j.aVUaA1k3eJak2a0.u11nO0/9ijsFX4W", // trackeroo + last_login: new Date(), + session_token: "", + issued_at: new Date(), + expires_at: new Date(Date.now() + 24*60*60*1000) + } +]); +db.devices.insertMany([ + { + _id: "trk-18608d12c8414acfcb9165b1", + name: "wonderful_wirth", + status: { connected: true, last_message: "2025-08-02T16:40:46Z" }, + device_type: "other", + private_key: "m9mFoEWSL+GwWtoyPZsF2q4HpwLddf7JfsxmedN8h+Y=", + created_at: ISODate("2025-08-18T03:36:46Z"), + }, + { + _id: "trk-18608d12c843cfc86452c8bc", + name: "xenodochial_carmack", + status: { connected: false, last_message: "2025-08-18T20:34:46Z" }, + device_type: "valuable", + private_key: "QNiUX1DdBq9hNGToa8ynDbXZRF9aDbO4LWvm7QspLo8=", + created_at: ISODate("2025-08-08T07:04:46Z"), + }, + { + _id: "trk-18608d12c8443834a667d6c2", + name: "merry_schwinger", + status: { connected: false, last_message: "2025-08-10T14:36:46Z" }, + device_type: "food", + private_key: "mS+C+G6Ut4mrbmz6v3EA5Fm7OvtcwrcRzvLOoIyIRDw=", + created_at: ISODate("2025-08-08T17:05:46Z"), + }, + { + _id: "trk-18608d12c8445101b3d3a3b8", + name: "thirsty_henry", + status: { connected: false, last_message: "2025-08-22T18:58:46Z" }, + device_type: "public_transport", + private_key: "4kKuUQSMJE9RckmbCFReQy5gtbRpY3sCgDjcTnxBxwI=", + created_at: ISODate("2025-08-04T05:03:46Z"), + }, + { + _id: "trk-18608d12c8446943ffbf2df9", + name: "admiring_bohr", + status: { connected: true, last_message: "2025-08-06T01:33:46Z" }, + device_type: "valuable", + private_key: "5mp9W9wUVd50eqGqC/JpW/0/pG6wBrAsivSPg0bcC2M=", + created_at: ISODate("2025-08-25T16:35:46Z"), + }, + { + _id: "trk-18608d12c84480bd0ad4656f", + name: "keen_bohr", + status: { connected: true, last_message: "2025-08-16T05:31:46Z" }, + device_type: "private_transport", + private_key: "/3tiwn6woUzwRGe/TUqOZQNGvYWHYSHj4d/Fpq9eTS8=", + created_at: ISODate("2025-08-01T14:09:46Z"), + }, + { + _id: "trk-18608d12c8449838a8eb28eb", + name: "clever_fermi", + status: { connected: true, last_message: "2025-08-08T23:13:46Z" }, + device_type: "valuable", + private_key: "pveqq6guV0EAQANRpMOQq5Yd8iSaZSH6QUMynO0ke6g=", + created_at: ISODate("2025-08-05T16:46:46Z"), + }, + { + _id: "trk-18608d12c844b01d0d668fce", + name: "brave_schrodinger", + status: { connected: false, last_message: "2025-08-04T16:28:46Z" }, + device_type: "valuable", + private_key: "+NrhtHayxZYPZnqA+C1C6uth3bORje/ka5uxXZ3E2zQ=", + created_at: ISODate("2025-08-04T20:21:46Z"), + }, + { + _id: "trk-18608d12c844cb262515236b", + name: "youthful_stallman", + status: { connected: false, last_message: "2025-08-22T21:24:46Z" }, + device_type: "valuable", + private_key: "dkvOCYpkWXr3WZs62jH9LfjOAbhm0TapSNQvcIzijLA=", + created_at: ISODate("2025-08-04T10:00:46Z"), + }, + { + _id: "trk-18608d12c844e58e640b9ea1", + name: "friendly_curie", + status: { connected: true, last_message: "2025-08-21T03:51:46Z" }, + device_type: "private_transport", + private_key: "1l0SNynykdBFZVNfGlkTYiniGlmk5KVgyW3bp8HI6xg=", + created_at: ISODate("2025-08-05T23:57:46Z"), + }, + { + _id: "trk-18608d12c844fccd26144714", + name: "charming_dirac", + status: { connected: false, last_message: "2025-07-31T23:08:46Z" }, + device_type: "public_transport", + private_key: "/Vi9D/RMiWilNw0jHA/fv4DbGwAEbqRAgQJulPl4DTQ=", + created_at: ISODate("2025-08-08T18:10:46Z"), + }, + { + _id: "trk-18608d12c8451452004ec96d", + name: "iron_franklin", + status: { connected: true, last_message: "2025-08-02T12:47:46Z" }, + device_type: "public_transport", + private_key: "V0exZwne86ptOGifjuWX7QmGjpN1W62szV6+WxX9m5s=", + created_at: ISODate("2025-08-07T12:55:46Z"), + }, + { + _id: "trk-18608d12c8456a397013ce6b", + name: "gentle_church", + status: { connected: false, last_message: "2025-08-11T23:09:46Z" }, + device_type: "other", + private_key: "KH+3BS3QnEec8vME0ToemUn3mK9i0bwCg1ZlvgnRfkU=", + created_at: ISODate("2025-08-15T20:23:46Z"), + }, + { + _id: "trk-18608d12c84581a0b80ca1c5", + name: "practical_michelson", + status: { connected: false, last_message: "2025-08-11T11:15:46Z" }, + device_type: "public_transport", + private_key: "ab3Wu7Glx9xhfSFUNRKzi1CZPfPeiCI7ujc/iJmYWpI=", + created_at: ISODate("2025-08-04T21:37:46Z"), + }, + { + _id: "trk-18608d12c845ad105b9a60db", + name: "phenomenal_born", + status: { connected: false, last_message: "2025-08-23T09:48:46Z" }, + device_type: "private_transport", + private_key: "h3dT1JuyA/MhH1/o/n/IiPRFEyyEvXlMl3ZNtrCHZwY=", + created_at: ISODate("2025-08-06T01:48:46Z"), + }, + { + _id: "trk-18608d12c845c8050d2973c1", + name: "agitated_planck", + status: { connected: false, last_message: "2025-08-20T06:43:46Z" }, + device_type: "food", + private_key: "l/TRQN08IpMfwVHCWHpcNQ/SBuRWLeNcuJZDf28697c=", + created_at: ISODate("2025-08-12T01:20:46Z"), + }, + { + _id: "trk-18608d12c845df659619c7e6", + name: "determined_kelvin", + status: { connected: true, last_message: "2025-08-16T14:01:46Z" }, + device_type: "valuable", + private_key: "MlxmbBIfcGNpReS6GUgLDmTRrDDQQ6rlLxj4Eggrfjo=", + created_at: ISODate("2025-08-10T07:50:46Z"), + }, + { + _id: "trk-18608d12c845f5d103e908ad", + name: "thoughtful_weber", + status: { connected: false, last_message: "2025-08-18T05:54:46Z" }, + device_type: "valuable", + private_key: "WGmlcdPBvesFWds8BZ4UR4k7ze/25P1GpWhXaSmgNDQ=", + created_at: ISODate("2025-08-04T22:08:46Z"), + }, + { + _id: "trk-18608d12c8460bf33167dbdc", + name: "hyper_shockley", + status: { connected: false, last_message: "2025-08-17T20:07:46Z" }, + device_type: "other", + private_key: "BzHUYgawXJvf458ZW6fLaKxbTEoOXBI1pgI4Z5ebMuM=", + created_at: ISODate("2025-08-17T07:01:46Z"), + }, + { + _id: "trk-18608d12c8462309d5e746cc", + name: "pious_planck", + status: { connected: true, last_message: "2025-08-12T19:59:46Z" }, + device_type: "private_transport", + private_key: "DMAF8lJbDW/hdBvKttHueeokLXgQ9nlcTQBDMfypO4w=", + created_at: ISODate("2025-08-15T02:45:46Z"), + }, + { + _id: "trk-18608d12c84640a54b9bd528", + name: "heuristic_fermi", + status: { connected: true, last_message: "2025-08-02T15:13:46Z" }, + device_type: "valuable", + private_key: "+crZGHsZ6r5ZN0UMsB0JFK/hveE6E267uzcAGdkQAZQ=", + created_at: ISODate("2025-08-09T02:32:46Z"), + }, + { + _id: "trk-18608d12c846577d4ec97cfb", + name: "sharp_edison", + status: { connected: true, last_message: "2025-08-14T03:13:46Z" }, + device_type: "private_transport", + private_key: "eW16LWeR4CcKm0mRqkBnv9lU6WYKXyzS8csOmoTwt+c=", + created_at: ISODate("2025-08-21T01:54:46Z"), + }, + { + _id: "trk-18608d12c8466e529b599dc4", + name: "romantic_hertz", + status: { connected: true, last_message: "2025-08-30T03:20:46Z" }, + device_type: "private_transport", + private_key: "kbRJ702ADyEPki+ko7MbOZ7AGvhKPSql5A7fK6MGFGI=", + created_at: ISODate("2025-08-17T20:13:46Z"), + }, + { + _id: "trk-18608d12c84685251cdaf35c", + name: "gallant_whitehead", + status: { connected: true, last_message: "2025-08-05T15:55:46Z" }, + device_type: "private_transport", + private_key: "ZeGHStsNcbp56gDEk+nasq/BaaLklfzXfuG8BcX+lrs=", + created_at: ISODate("2025-08-28T00:41:46Z"), + }, + { + _id: "trk-18608d12c8469c00840cc49b", + name: "laughing_rutherford", + status: { connected: false, last_message: "2025-08-13T10:56:46Z" }, + device_type: "private_transport", + private_key: "n0LNbNhaIaW42aBkqCwDM0cJSwcwL8McL8im5CP91v4=", + created_at: ISODate("2025-08-29T17:24:46Z"), + }, + { + _id: "trk-18608d12c846b3600123a417", + name: "sad_marconi", + status: { connected: true, last_message: "2025-08-05T23:52:46Z" }, + device_type: "food", + private_key: "g2D4mb6ezOrfK/ft41lACByyeU4IQVF6RWlB/0P7zhc=", + created_at: ISODate("2025-08-15T07:38:46Z"), + }, + { + _id: "trk-18608d12c846ca7bab489e30", + name: "clever_newton", + status: { connected: true, last_message: "2025-08-18T06:39:46Z" }, + device_type: "valuable", + private_key: "hGz/4/osEsLT/5Q53TTlID+wA54Xb2p8Cxpj05jZsik=", + created_at: ISODate("2025-08-18T00:25:46Z"), + }, + { + _id: "trk-18608d12c846e0a3e38974e5", + name: "dazzling_maxwell", + status: { connected: false, last_message: "2025-08-10T04:46:46Z" }, + device_type: "other", + private_key: "KSG9WiZ6pWINfm9FH9vS/GSo3t1TmLf+eE5eGhU+Hik=", + created_at: ISODate("2025-08-05T06:51:46Z"), + }, + { + _id: "trk-18608d12c847064ce1bdf7ee", + name: "elegant_lagrange", + status: { connected: true, last_message: "2025-08-28T18:21:46Z" }, + device_type: "public_transport", + private_key: "tlrnwpslaBwOChP+dAFPQ2sfItsoKERU+MR33NHk9yQ=", + created_at: ISODate("2025-08-19T09:04:46Z"), + }, + { + _id: "trk-18608d12c8472b9f0905b3ad", + name: "ecstatic_riemann", + status: { connected: true, last_message: "2025-08-05T19:27:46Z" }, + device_type: "private_transport", + private_key: "5v94oQP8pNQZrOZrYNTTl5c67qyrPDUJE4eZTfuBjVU=", + created_at: ISODate("2025-08-08T11:59:46Z"), + }, + { + _id: "trk-18608d12c84742df27b88f6b", + name: "cranky_ampere", + status: { connected: false, last_message: "2025-08-19T19:18:46Z" }, + device_type: "other", + private_key: "cS34G/an+Z+MM1M5P5dh48B+y4kjyy0eI/8cw4ClkT4=", + created_at: ISODate("2025-08-08T18:36:46Z"), + }, + { + _id: "trk-18608d12c84759c8a9ef04f8", + name: "energetic_hilbert", + status: { connected: true, last_message: "2025-08-25T05:04:46Z" }, + device_type: "public_transport", + private_key: "EInJWD1jh2t06pz3D5WZjspYvxzqs57NRKAxVhe9uIk=", + created_at: ISODate("2025-08-28T01:57:46Z"), + }, + { + _id: "trk-18608d12c84770f5153b97ef", + name: "amazing_turing", + status: { connected: false, last_message: "2025-08-10T10:51:46Z" }, + device_type: "other", + private_key: "X9Lx6+KllLFtNDyvj/466CrOQmF6UewS+3xs9j18ILA=", + created_at: ISODate("2025-08-03T11:49:46Z"), + }, + { + _id: "trk-18608d12c84786f21e4edc5e", + name: "happy_einstein", + status: { connected: true, last_message: "2025-08-21T12:01:46Z" }, + device_type: "private_transport", + private_key: "wzTOvpERsSN6QO4jgUD3XuRtcdqQLItdjSxSgrKmcoo=", + created_at: ISODate("2025-08-17T21:31:46Z"), + }, + { + _id: "trk-18608d12c8479d828433ff9a", + name: "funny_peano", + status: { connected: true, last_message: "2025-08-11T05:13:46Z" }, + device_type: "private_transport", + private_key: "cqseF4l1F5lAgCe7bD6EP+rEE+L1wFhc+10MQV48UJo=", + created_at: ISODate("2025-08-24T04:47:46Z"), + }, + { + _id: "trk-18608d12c847b3e74258eb03", + name: "vibrant_thompson", + status: { connected: false, last_message: "2025-08-15T07:02:46Z" }, + device_type: "private_transport", + private_key: "O0XXGhe7rp/EsEYyXNAGIKjJk5T+FXCAdebGqh/dluo=", + created_at: ISODate("2025-08-16T09:54:46Z"), + }, + { + _id: "trk-18608d12c8480b18fe88b4af", + name: "patient_wu", + status: { connected: false, last_message: "2025-08-21T22:36:46Z" }, + device_type: "public_transport", + private_key: "bHzGkyOqJLG9GAnkC25B2OLoXhQhIUu3bPVIE9sBlzU=", + created_at: ISODate("2025-08-24T18:56:46Z"), + }, + { + _id: "trk-18608d12c84823aa3e06b7da", + name: "epic_cantor", + status: { connected: true, last_message: "2025-08-07T10:15:46Z" }, + device_type: "private_transport", + private_key: "G7MRIMBP24R0xdEXZRIV3me9dDDFRdxtTXuLXe28494=", + created_at: ISODate("2025-08-02T10:17:46Z"), + }, + { + _id: "trk-18608d12c8483aeb4dce2724", + name: "hopeful_bardeen", + status: { connected: true, last_message: "2025-08-30T06:16:46Z" }, + device_type: "public_transport", + private_key: "Xd5a7XzvWCVjDtduORbuAH1sykFtGHcvAhK/4BadcaI=", + created_at: ISODate("2025-08-06T23:01:46Z"), + }, + { + _id: "trk-18608d12c8486259917b5535", + name: "frosty_dedekind", + status: { connected: false, last_message: "2025-08-07T10:35:46Z" }, + device_type: "private_transport", + private_key: "I0ahyNHzwY2ax1RTIwkdAW1+4eErU6s1WISzCED2ImI=", + created_at: ISODate("2025-08-05T02:16:46Z"), + }, + { + _id: "trk-18608d12c8487a58b5c5651d", + name: "heartwarming_bose", + status: { connected: true, last_message: "2025-08-15T11:05:46Z" }, + device_type: "other", + private_key: "LsgbCoMQG+opSUt9JrsiDIL4Z1k5VU+WnTGajUsfRMU=", + created_at: ISODate("2025-08-04T01:01:46Z"), + }, + { + _id: "trk-18608d12c848919ae0a5ac8f", + name: "kind_torvalds", + status: { connected: false, last_message: "2025-08-02T19:55:46Z" }, + device_type: "food", + private_key: "ij0mk0VXVo7nsb0cqpGJX0V5oChhDa830+CJ72ZW8xk=", + created_at: ISODate("2025-08-20T23:21:46Z"), + }, + { + _id: "trk-18608d12c848b620cb14aa25", + name: "fascinated_noether", + status: { connected: false, last_message: "2025-08-20T21:05:46Z" }, + device_type: "other", + private_key: "XJVLbd6UO12QkuIGo3EbZGXjG4xPKSvliiOLRLnMZmI=", + created_at: ISODate("2025-08-09T07:11:46Z"), + }, + { + _id: "trk-18608d12c848d48480a5d5b6", + name: "puzzled_fizeau", + status: { connected: true, last_message: "2025-08-22T09:43:46Z" }, + device_type: "food", + private_key: "ceSr36+tty8/YgIQuT7V66kMAXhi+K2JPN7EqEF7r3Y=", + created_at: ISODate("2025-08-02T11:41:46Z"), + }, + { + _id: "trk-18608d12c848eb34f8760e19", + name: "competent_rutherford", + status: { connected: true, last_message: "2025-08-04T03:29:46Z" }, + device_type: "valuable", + private_key: "e8kkc8+QotgP718tO8zL2+ukDwUiNfSmRl3UQFKnEwc=", + created_at: ISODate("2025-08-02T23:24:46Z"), + }, + { + _id: "trk-18608d12c8490334a6303366", + name: "pedantic_pauli", + status: { connected: true, last_message: "2025-08-30T09:45:46Z" }, + device_type: "private_transport", + private_key: "kXVOIOGZGLfPzYCPK55aVbR15kbwTZr9ezA6zSxHEig=", + created_at: ISODate("2025-08-05T01:38:46Z"), + }, + { + _id: "trk-18608d12c8491a91b1f888a9", + name: "magical_dirac", + status: { connected: false, last_message: "2025-08-08T15:21:46Z" }, + device_type: "valuable", + private_key: "C9biy8JKSu1nQCKQxGTjuxkjHr4W46086h3KwjVBr/I=", + created_at: ISODate("2025-08-05T22:46:46Z"), + }, + { + _id: "trk-18608d12c84931d6fa2d6232", + name: "serene_tesla", + status: { connected: false, last_message: "2025-08-03T19:17:46Z" }, + device_type: "private_transport", + private_key: "xPGoSwT+dTYd6ZX1++0+CJC8iEwVscx3VoDDI+LXWw8=", + created_at: ISODate("2025-08-05T07:06:46Z"), + }, + { + _id: "trk-18608d12c8494897112ba78e", + name: "peaceful_pascal", + status: { connected: true, last_message: "2025-08-05T08:37:46Z" }, + device_type: "other", + private_key: "WAVVSnHd8206VRwJOtgHDuUGDf0orqqYBIqodJA9L1I=", + created_at: ISODate("2025-08-15T05:44:46Z"), + }, + { + _id: "trk-18608d12c8495fd71857ce3f", + name: "stoic_ohm", + status: { connected: false, last_message: "2025-08-22T12:36:46Z" }, + device_type: "food", + private_key: "vzDwSMp/L6oyVEyYj3RKTY9UKyBj3V7LvhCD6cqe/nI=", + created_at: ISODate("2025-08-22T20:43:46Z"), + }, + { + _id: "trk-18608d12c84976fefd57d1ff", + name: "fearless_galois", + status: { connected: false, last_message: "2025-08-10T16:04:46Z" }, + device_type: "private_transport", + private_key: "PoHtJ2l8pTjJPJctSMrIII20agqTIk4LpVoiIdNLzgo=", + created_at: ISODate("2025-08-03T14:47:46Z"), + }, + { + _id: "trk-18608d12c8498d777941e066", + name: "distracted_planck", + status: { connected: true, last_message: "2025-08-24T07:49:46Z" }, + device_type: "food", + private_key: "W8F6g6HmjQAP3lwJjRqAfdQOIOsSq1vzEVbHMS8ZUAo=", + created_at: ISODate("2025-08-21T20:41:46Z"), + }, + { + _id: "trk-18608d12c849a50c845c25a9", + name: "optimistic_babbage", + status: { connected: true, last_message: "2025-08-12T09:06:46Z" }, + device_type: "valuable", + private_key: "zpX14uHmth5/ZzzNaL68qeoHLJRZjT3huaYns0LmnWU=", + created_at: ISODate("2025-08-06T17:19:46Z"), + }, + { + _id: "trk-18608d12c849bd6d485879c1", + name: "tender_faraday", + status: { connected: true, last_message: "2025-08-10T12:57:46Z" }, + device_type: "public_transport", + private_key: "O8XxluA24EC4bsgPrBHWCwS8q8GmBK7mv0byordbYc8=", + created_at: ISODate("2025-08-13T13:09:46Z"), + }, + { + _id: "trk-18608d12c849d409351613c2", + name: "jaunty_pauling", + status: { connected: true, last_message: "2025-08-27T07:44:46Z" }, + device_type: "public_transport", + private_key: "OLbAlVzxkhzr2wlaO/WV8fCB/m4jJQ5AIfrJ0tUgF0A=", + created_at: ISODate("2025-08-20T22:21:46Z"), + }, + { + _id: "trk-18608d12c84a2e54a6ffdd76", + name: "suspicious_volta", + status: { connected: true, last_message: "2025-08-03T18:04:46Z" }, + device_type: "valuable", + private_key: "S30Z2mlkvsUR6yH8nULY8RqPhmUcxEHEcEVuP7ePmSM=", + created_at: ISODate("2025-08-10T10:53:46Z"), + }, + { + _id: "trk-18608d12c84a5f766256c9e2", + name: "serene_hopper", + status: { connected: true, last_message: "2025-08-13T10:31:46Z" }, + device_type: "food", + private_key: "DTOj/Y2DiO0Ju5vwxOUgBtwXB5579pz30EEMYun7z00=", + created_at: ISODate("2025-08-18T16:45:46Z"), + }, + { + _id: "trk-18608d12c84a892c3aad3955", + name: "eager_gauss", + status: { connected: false, last_message: "2025-08-06T19:07:46Z" }, + device_type: "public_transport", + private_key: "Dl4fLpzCBXqNHjtrgm+5jrj+tSXPZRxttltQPvr/JcY=", + created_at: ISODate("2025-08-29T10:30:46Z"), + }, + { + _id: "trk-18608d12c84aa049b09a2169", + name: "eager_darwin", + status: { connected: true, last_message: "2025-08-10T19:58:46Z" }, + device_type: "other", + private_key: "EC7AoSY05EyPbRVEl/eGTSfESGXZZ4OWF5FsPcaKdTU=", + created_at: ISODate("2025-08-24T18:39:46Z"), + }, + { + _id: "trk-18608d12c84ab6d05b8082ad", + name: "hardcore_bell", + status: { connected: true, last_message: "2025-08-25T03:22:46Z" }, + device_type: "food", + private_key: "Ar3WbxWHU8l2uyOZPnqJffGQ9vJpXAovM/ZoNRYd9RA=", + created_at: ISODate("2025-08-20T22:33:46Z"), + }, + { + _id: "trk-18608d12c84acd0d1c7fc74a", + name: "enchanting_poincare", + status: { connected: false, last_message: "2025-08-12T07:20:46Z" }, + device_type: "private_transport", + private_key: "0HbmI0I5esubDm4b6XvzgLgHJtXVxGTeIt4EiHJAKc0=", + created_at: ISODate("2025-08-08T23:41:46Z"), + }, + { + _id: "trk-18608d12c84ae3dffff2e52d", + name: "proud_morley", + status: { connected: true, last_message: "2025-08-17T10:23:46Z" }, + device_type: "private_transport", + private_key: "NYrTqrlsGqPo9r6nKF2+bVd+QshvHb00Z676LpK3R00=", + created_at: ISODate("2025-08-30T11:59:46Z"), + }, + { + _id: "trk-18608d12c84afa2c10aa6c55", + name: "inspiring_bardeen", + status: { connected: false, last_message: "2025-08-15T18:29:46Z" }, + device_type: "other", + private_key: "6dU2H5RdD5VaZyXi5ZWaj5rkydlEV5f2PS1l9+fvL+c=", + created_at: ISODate("2025-08-07T16:21:46Z"), + }, + { + _id: "trk-18608d12c84b11c367999861", + name: "objective_glashow", + status: { connected: true, last_message: "2025-08-26T14:04:46Z" }, + device_type: "private_transport", + private_key: "uW2kufM6EeR+tpJHDYRmd/5CPGLMn84jMBsu6mhWtRU=", + created_at: ISODate("2025-08-19T03:27:46Z"), + }, + { + _id: "trk-18608d12c84b2feca3792861", + name: "modest_dyson", + status: { connected: false, last_message: "2025-08-25T10:25:46Z" }, + device_type: "public_transport", + private_key: "5XYG/w/clJolar2hAO3n4D9+fJdckv7i0ZO0S2m2KRo=", + created_at: ISODate("2025-08-02T00:09:46Z"), + }, + { + _id: "trk-18608d12c84b469627a640d9", + name: "boring_heisenberg", + status: { connected: false, last_message: "2025-08-25T03:19:46Z" }, + device_type: "food", + private_key: "EHPvNh8KnmQNbD8Kef2UZh+07Y6DiHKpNwTuuIcyemo=", + created_at: ISODate("2025-08-10T16:52:46Z"), + }, + { + _id: "trk-18608d12c84b5cae4cff3133", + name: "strange_ampere", + status: { connected: true, last_message: "2025-08-28T04:31:46Z" }, + device_type: "private_transport", + private_key: "fUqc7PB2fR3likQIUigerur96bUzz7dQZE/lMUiMJHY=", + created_at: ISODate("2025-08-08T07:02:46Z"), + }, + { + _id: "trk-18608d12c84b74702fe3e85c", + name: "faithful_hardy", + status: { connected: false, last_message: "2025-08-27T20:37:46Z" }, + device_type: "private_transport", + private_key: "etflzQEpsxaJqAlO2o/G45T+s80cWWaSV0KPobuWWFE=", + created_at: ISODate("2025-08-02T15:50:46Z"), + }, + { + _id: "trk-18608d12c84b8b83746dbc8b", + name: "eloquent_leibniz", + status: { connected: true, last_message: "2025-08-14T14:03:46Z" }, + device_type: "food", + private_key: "nwdfO4d3o5nmJsO/i8xQrgcjsa2KNSQQ9PQzgMNbEc8=", + created_at: ISODate("2025-08-13T23:17:46Z"), + }, + { + _id: "trk-18608d12c84ba2911c4a675e", + name: "cool_ohm", + status: { connected: false, last_message: "2025-08-12T06:22:46Z" }, + device_type: "other", + private_key: "7TXa0utsnnJXx7tHWYt5KC8nwv1xThn8iy12KI2zOvE=", + created_at: ISODate("2025-08-12T01:49:46Z"), + }, + { + _id: "trk-18608d12c84bde9e8b791f18", + name: "fervent_abel", + status: { connected: true, last_message: "2025-08-03T23:36:46Z" }, + device_type: "other", + private_key: "1doRBvIJsrXQIG1Ui9albOMUHxMjtrjU6VvNLZTZQak=", + created_at: ISODate("2025-08-29T22:05:46Z"), + }, + { + _id: "trk-18608d12c84bf63dcc1fb3e7", + name: "groovy_shannon", + status: { connected: true, last_message: "2025-08-10T16:58:46Z" }, + device_type: "food", + private_key: "4NnTHCSG64xQf807OjIJlzc+MdgrOv9/a83gDgRzUwQ=", + created_at: ISODate("2025-08-16T23:38:46Z"), + }, + { + _id: "trk-18608d12c84c23101fa0a8b6", + name: "grieving_wiener", + status: { connected: true, last_message: "2025-08-14T09:38:46Z" }, + device_type: "food", + private_key: "BpChhURWcEyErTfXhnx6J9gg4L+Qy4FyWzdeL6e27u4=", + created_at: ISODate("2025-08-14T21:20:46Z"), + }, + { + _id: "trk-18608d12c84c3ae0fd8aad7e", + name: "optimized_higgs", + status: { connected: false, last_message: "2025-08-21T14:05:46Z" }, + device_type: "other", + private_key: "csXqVuqw2AXm2A46aCt97ytsUzs5pA6t5Jnit25JFkI=", + created_at: ISODate("2025-08-15T19:27:46Z"), + }, + { + _id: "trk-18608d12c84c5151bb792931", + name: "boring_wozniak", + status: { connected: true, last_message: "2025-08-08T02:27:46Z" }, + device_type: "food", + private_key: "X3CHEXehWzXvRfQAJ6GGCoeEbVPcdX95JCzT3uEBYSs=", + created_at: ISODate("2025-08-02T10:22:46Z"), + }, + { + _id: "trk-18608d12c84c678173241105", + name: "curious_feynman", + status: { connected: false, last_message: "2025-08-21T18:29:46Z" }, + device_type: "public_transport", + private_key: "XxN0HpCbfzUE2AMRPcteDPSRLT5YgnAy4ILOxwIzm/g=", + created_at: ISODate("2025-08-26T19:07:46Z"), + }, + { + _id: "trk-18608d12c84c7e6385d059d6", + name: "noble_weinberg", + status: { connected: true, last_message: "2025-08-07T01:06:46Z" }, + device_type: "valuable", + private_key: "SZzpdjeGSaaWEBBa8apWtQJdwscbjyJ4jC/nXgnxJu4=", + created_at: ISODate("2025-08-21T16:34:46Z"), + }, + { + _id: "trk-18608d12c84c9470140d32f6", + name: "upbeat_ritchie", + status: { connected: false, last_message: "2025-08-10T09:04:46Z" }, + device_type: "private_transport", + private_key: "65mhzIpZvOx51yE6nPxQgjgy3rwlv13lMSM5X7vEFeQ=", + created_at: ISODate("2025-08-01T12:55:46Z"), + }, + { + _id: "trk-18608d12c84caba1375500b4", + name: "quizzical_doppler", + status: { connected: true, last_message: "2025-08-11T14:42:46Z" }, + device_type: "food", + private_key: "t/9N9FYcN12suJ/nZl5q6jC7OAWv0h2Pow6vJBSr4dA=", + created_at: ISODate("2025-08-29T00:09:46Z"), + }, + { + _id: "trk-18608d12c84cc3de436f7bde", + name: "silly_westinghouse", + status: { connected: true, last_message: "2025-08-28T04:22:46Z" }, + device_type: "food", + private_key: "Ba4MVIb1YQL6dm2eYRiJOX0CljrdeuFtAI2HZ3nBtsE=", + created_at: ISODate("2025-08-02T07:07:46Z"), + }, + { + _id: "trk-18608d12c84cdbbc4c9327d2", + name: "dreamy_tesla", + status: { connected: true, last_message: "2025-08-12T22:50:46Z" }, + device_type: "other", + private_key: "/kDru3xkiT7oAfKFD4Mp6nVl5IIFEMXponchC0ojKlo=", + created_at: ISODate("2025-08-01T00:21:46Z"), + }, + { + _id: "trk-18608d12c84cf3df4741680f", + name: "jovial_mendeleev", + status: { connected: true, last_message: "2025-08-06T08:10:46Z" }, + device_type: "valuable", + private_key: "voqhD27X8L+VW4e/Op93AyazBBx/a7mqhTbAsvn6tnc=", + created_at: ISODate("2025-08-26T23:38:46Z"), + }, + { + _id: "trk-18608d12c84d0b5fc332f041", + name: "great_kolmogorov", + status: { connected: false, last_message: "2025-08-14T09:59:46Z" }, + device_type: "valuable", + private_key: "TH/tO2mmlgP3dnH6h+ickwu8IaEajnm+DpvDLcAZDC8=", + created_at: ISODate("2025-08-29T11:27:46Z"), + }, + { + _id: "trk-18608d12c84d2247a477b6ff", + name: "interesting_watson", + status: { connected: true, last_message: "2025-08-20T09:29:46Z" }, + device_type: "other", + private_key: "ZBNn287pTaxPwGShBOOICVcZDmQbcdr2vtmLYbelgoU=", + created_at: ISODate("2025-08-19T19:46:46Z"), + }, + { + _id: "trk-18608d12c84d385e1d5c7bbc", + name: "zealous_berners", + status: { connected: true, last_message: "2025-08-04T11:23:46Z" }, + device_type: "private_transport", + private_key: "5Wx/7819jHalM4yOsTrhtswnTS8ItR5/n0BEaYFotQM=", + created_at: ISODate("2025-08-18T18:58:46Z"), + }, + { + _id: "trk-18608d12c84d62f4adb3c923", + name: "condescending_volta", + status: { connected: true, last_message: "2025-08-15T00:32:46Z" }, + device_type: "food", + private_key: "CKwOOv0LRsGpDURy9iXE9tnUFdjtI6ZqtAFoJkal360=", + created_at: ISODate("2025-08-26T02:07:46Z"), + }, + { + _id: "trk-18608d12c84d9ce75be19956", + name: "adoring_morse", + status: { connected: false, last_message: "2025-08-28T04:09:46Z" }, + device_type: "private_transport", + private_key: "iL0T7kaXpwPrO6Z0mA7Kk2Gs+tHEBl6fNSbFV3rSgVM=", + created_at: ISODate("2025-08-04T14:20:46Z"), + }, + { + _id: "trk-18608d12c84db5aa45ada38e", + name: "elastic_fourier", + status: { connected: true, last_message: "2025-08-22T06:00:46Z" }, + device_type: "public_transport", + private_key: "8loHVYewF31YJGSlkcOrMZpfc5eQ2DFhuvpne8M68Xc=", + created_at: ISODate("2025-08-09T06:44:46Z"), + }, + { + _id: "trk-18608d12c84dccaa3ac843bd", + name: "thrilled_gauss", + status: { connected: false, last_message: "2025-08-14T07:07:46Z" }, + device_type: "public_transport", + private_key: "jhESuO3AwXCtX0TcH5E9l91DDRUUx3SIErqpjt+SpUk=", + created_at: ISODate("2025-08-25T19:51:46Z"), + }, + { + _id: "trk-18608d12c84de3a8df5efbcc", + name: "goofy_markov", + status: { connected: false, last_message: "2025-08-20T02:12:46Z" }, + device_type: "public_transport", + private_key: "1bmj0Qvq4D9EsyHZJpITpEqEBBrOcD9KsiUhDX4X7R4=", + created_at: ISODate("2025-08-27T19:33:46Z"), + }, + { + _id: "trk-18608d12c84dfa6590d8fbda", + name: "relaxed_turing", + status: { connected: true, last_message: "2025-08-29T02:47:46Z" }, + device_type: "private_transport", + private_key: "bMvQyF6Lu/h5zYPEn4TBAOAxjVj67QTXl3ifZYf3S7s=", + created_at: ISODate("2025-08-09T23:16:46Z"), + }, + { + _id: "trk-18608d12c84e12b161fb52f1", + name: "exciting_godel", + status: { connected: true, last_message: "2025-08-19T23:11:46Z" }, + device_type: "valuable", + private_key: "L+ZecOMBWt0L7Ix8sPW8MZpDskk30MZydLOV1av++fk=", + created_at: ISODate("2025-08-30T12:59:46Z"), + }, + { + _id: "trk-18608d12c84e2a4a52da79d9", + name: "sweet_galvani", + status: { connected: false, last_message: "2025-08-29T21:21:46Z" }, + device_type: "valuable", + private_key: "dDVOk5b+i7+npM3o7icJocZSNFaed64RKX6HuxK5TTk=", + created_at: ISODate("2025-08-22T08:08:46Z"), + }, + { + _id: "trk-18608d12c84e473a6d897698", + name: "fabulous_ramanujan", + status: { connected: false, last_message: "2025-08-18T06:15:46Z" }, + device_type: "private_transport", + private_key: "n/7uqrA1Xh7uzYaX4BouodyLFlU/HWXMF3Gjd5gRN6g=", + created_at: ISODate("2025-08-19T16:05:46Z"), + }, + { + _id: "trk-18608d12c84e67b817e2a8fd", + name: "trusting_knuth", + status: { connected: true, last_message: "2025-08-28T09:04:46Z" }, + device_type: "private_transport", + private_key: "frrz+vzXLVQV4HHhZf7MFMPo4iGIJQWsgSPIkHKJ8OE=", + created_at: ISODate("2025-08-01T15:20:46Z"), + }, + { + _id: "trk-18608d12c84e7f725b9dd529", + name: "quirky_shannon", + status: { connected: false, last_message: "2025-08-16T17:49:46Z" }, + device_type: "food", + private_key: "443NJMtr5p+i0QIA8th2B9QTsh3aKhI1p51G0uwN1iQ=", + created_at: ISODate("2025-08-09T21:36:46Z"), + }, + { + _id: "trk-18608d12c84e95effa9dc925", + name: "nervous_hawking", + status: { connected: true, last_message: "2025-08-15T23:40:46Z" }, + device_type: "food", + private_key: "XTAux5J/tThXxN+5AoXR61Pxm0nfvJ92SOpCVo9HVpk=", + created_at: ISODate("2025-08-18T09:24:46Z"), + }, + { + _id: "trk-18608d12c84eaef489342f7e", + name: "exotic_turing", + status: { connected: true, last_message: "2025-08-03T15:10:46Z" }, + device_type: "public_transport", + private_key: "rrNb0cdFEBeDwGfwZXv8U1VqHz4QfxT6Re+o+HR8vGA=", + created_at: ISODate("2025-08-16T03:12:46Z"), + }, + { + _id: "trk-18608d12c84ec5eea3a8dae1", + name: "gifted_kleene", + status: { connected: true, last_message: "2025-08-12T00:58:46Z" }, + device_type: "valuable", + private_key: "EaBPc3d6Edkfi3YYxsqc+sAMILGwn2RbsIkB4zmnSU0=", + created_at: ISODate("2025-08-24T06:35:46Z"), + }, + { + _id: "trk-18608d12c84ee5a2e49c6f83", + name: "lucid_heisenberg", + status: { connected: true, last_message: "2025-08-08T11:32:46Z" }, + device_type: "valuable", + private_key: "Rb/XilBAyjcoGfgM4JkwZbZNEZsdf5pNWUXtF9/NqR8=", + created_at: ISODate("2025-08-19T05:39:46Z"), + }, +]); diff --git a/rabbitmq/etc/enabled_plugins b/rabbitmq/etc/enabled_plugins new file mode 100644 index 00000000..111ab160 --- /dev/null +++ b/rabbitmq/etc/enabled_plugins @@ -0,0 +1 @@ +[rabbitmq_auth_backend_http,rabbitmq_event_exchange,rabbitmq_management,rabbitmq_mqtt]. diff --git a/trackeroo-backend/rabbitmq/etc/rabbitmq.conf b/rabbitmq/etc/rabbitmq.conf similarity index 100% rename from trackeroo-backend/rabbitmq/etc/rabbitmq.conf rename to rabbitmq/etc/rabbitmq.conf diff --git a/services/inventory/group_vars /all.yml b/services/inventory/group_vars /all.yml index e69de29b..7e27e441 100644 --- a/services/inventory/group_vars /all.yml +++ b/services/inventory/group_vars /all.yml @@ -0,0 +1 @@ +rabbitmq_service_ip: 10.20.30.120 diff --git a/services/inventory/hosts.yml b/services/inventory/hosts.yml index 007a6c46..1b6959d9 100644 --- a/services/inventory/hosts.yml +++ b/services/inventory/hosts.yml @@ -2,6 +2,7 @@ all: vars: ansible_user: k3s-admin ansible_become: true + rabbitmq_service_ip: 10.20.30.120 servers: hosts: diff --git a/services/playbooks/roles/brokeroo/defaults/main.yml b/services/playbooks/roles/brokeroo/defaults/main.yml new file mode 100644 index 00000000..45b73a25 --- /dev/null +++ b/services/playbooks/roles/brokeroo/defaults/main.yml @@ -0,0 +1,7 @@ +brokeroo_namespace: trackeroo +brokeroo_app_name: brokeroo + +brokeroo_image: ghcr.io/skiby7/brokeroo:latest + +brokeroo_replicas: 3 +brokeroo_port: 8080 diff --git a/services/playbooks/roles/brokeroo/tasks/main.yml b/services/playbooks/roles/brokeroo/tasks/main.yml new file mode 100644 index 00000000..b4bb75ef --- /dev/null +++ b/services/playbooks/roles/brokeroo/tasks/main.yml @@ -0,0 +1,17 @@ +- name: "Create Brokeroo Kubernetes namespace" + kubernetes.core.k8s: + kubeconfig: /etc/rancher/k3s/k3s.yaml + name: "{{ brokeroo_namespace }}" + api_version: v1 + kind: Namespace + state: present + +- name: "Deploy Brokeroo Deployment and Service" + kubernetes.core.k8s: + kubeconfig: /etc/rancher/k3s/k3s.yaml + state: present + namespace: "{{ brokeroo_namespace }}" + definition: "{{ lookup('template', item) | from_yaml }}" + loop: + - "01-deployment.yml.j2" + - "02-service.yml.j2" diff --git a/services/playbooks/roles/brokeroo/templates/01-deployment.yml.j2 b/services/playbooks/roles/brokeroo/templates/01-deployment.yml.j2 new file mode 100644 index 00000000..cc1ae1e9 --- /dev/null +++ b/services/playbooks/roles/brokeroo/templates/01-deployment.yml.j2 @@ -0,0 +1,50 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ brokeroo_app_name }} + labels: + app: {{ brokeroo_app_name }} +spec: + nodeSelector: + workload-type: apps + replicas: {{ brokeroo_replicas }} + selector: + matchLabels: + app: {{ brokeroo_app_name }} + template: + metadata: + labels: + app: {{ brokeroo_app_name }} + spec: + nodeSelector: + workload-type: apps + containers: + - name: {{ brokeroo_app_name }} + image: {{ brokeroo_image }} + imagePullPolicy: Always + ports: + - containerPort: {{ brokeroo_port }} + env: + - name: MQTT_BROKER + value: "mqtt://rabbitmq:1883" + - name: MQTT_CLIENT_ID + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: MQTT_USERNAME + value: "apps" + - name: MQTT_PASSWORD + value: "apps" + - name: POSTGRES_URL + value: "postgres://apps:apps@tsdb-rw.data.svc.cluster.local:5432/tracker_db?sslmode=disable" + - name: TOPIC_PATTERN + value: "j/data/+/+" + - name: KAFKA_BROKER + value: "kafka.data.svc.cluster.local:9092" + resources: + requests: + cpu: "250m" + memory: "256Mi" + limits: + cpu: "1" + memory: "512Mi" diff --git a/services/playbooks/roles/brokeroo/templates/02-service.yml.j2 b/services/playbooks/roles/brokeroo/templates/02-service.yml.j2 new file mode 100644 index 00000000..4b037f9e --- /dev/null +++ b/services/playbooks/roles/brokeroo/templates/02-service.yml.j2 @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ brokeroo_app_name }} +spec: + selector: + app: {{ brokeroo_app_name }} + ports: + - name: http + port: {{ brokeroo_port }} + targetPort: {{ brokeroo_port }} + type: ClusterIP diff --git a/services/playbooks/roles/flink/defaults/main.yml b/services/playbooks/roles/flink/defaults/main.yml new file mode 100644 index 00000000..f192784d --- /dev/null +++ b/services/playbooks/roles/flink/defaults/main.yml @@ -0,0 +1,15 @@ +flink_namespace: data + +flink_image: flink:1.20.0-scala_2.12 + +flink_job_image: ghcr.io/skiby7/flink_job:latest + +flink_job_parallelism: 16 +flink_jobmanager_port: 8081 +flink_rpc_port_1: 6123 +flink_rpc_port_2: 6124 +flink_task_slots: 16 +flink_rpc: flink-jobmanager.data.svc.cluster.local + +postgres_url: jdbc:postgresql://tsdb-rw.data.svc.cluster.local:5432/tracker_db?sslmode=disable +kafka_url: kafka.data.svc.cluster.local:9092 diff --git a/services/playbooks/roles/flink/tasks/main.yml b/services/playbooks/roles/flink/tasks/main.yml new file mode 100644 index 00000000..62913f33 --- /dev/null +++ b/services/playbooks/roles/flink/tasks/main.yml @@ -0,0 +1,19 @@ +- name: "Create Flink Kubernetes namespace" + kubernetes.core.k8s: + kubeconfig: /etc/rancher/k3s/k3s.yaml + name: "{{ flink_namespace }}" + api_version: v1 + kind: Namespace + state: present + +- name: "Deploy Flink JobManager and TaskManager" + kubernetes.core.k8s: + kubeconfig: /etc/rancher/k3s/k3s.yaml + state: present + namespace: "{{ flink_namespace }}" + definition: "{{ lookup('template', item) | from_yaml }}" + loop: + - "01-jobmanager.yml.j2" + - "02-taskmanager.yml.j2" + - "03-job.yml.j2" + - "04-jobservice.yml.j2" \ No newline at end of file diff --git a/services/playbooks/roles/flink/templates/01-jobmanager.yml.j2 b/services/playbooks/roles/flink/templates/01-jobmanager.yml.j2 new file mode 100644 index 00000000..19a7cd99 --- /dev/null +++ b/services/playbooks/roles/flink/templates/01-jobmanager.yml.j2 @@ -0,0 +1,44 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: flink-jobmanager + labels: + app: flink-jobmanager +spec: + replicas: 1 + selector: + matchLabels: + app: flink-jobmanager + template: + metadata: + labels: + app: flink-jobmanager + spec: + nodeSelector: + workload-type: apps + containers: + - name: jobmanager + image: {{ flink_image }} + imagePullPolicy: Always + args: ["jobmanager"] + ports: + - containerPort: {{ flink_jobmanager_port }} + - containerPort: {{ flink_rpc_port_1 }} + - containerPort: {{ flink_rpc_port_2 }} + env: + - name: FLINK_PROPERTIES + value: | + jobmanager.rpc.address: {{ flink_rpc }} + resources: + requests: + cpu: 250m + memory: 512Mi + limits: + cpu: 500m + memory: 1Gi + readinessProbe: + httpGet: + path: /overview + port: {{ flink_jobmanager_port }} + initialDelaySeconds: 10 + periodSeconds: 5 diff --git a/services/playbooks/roles/flink/templates/02-taskmanager.yml.j2 b/services/playbooks/roles/flink/templates/02-taskmanager.yml.j2 new file mode 100644 index 00000000..5d1766f4 --- /dev/null +++ b/services/playbooks/roles/flink/templates/02-taskmanager.yml.j2 @@ -0,0 +1,36 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: flink-taskmanager + labels: + app: flink-taskmanager +spec: + replicas: 1 + selector: + matchLabels: + app: flink-taskmanager + template: + metadata: + labels: + app: flink-taskmanager + spec: + securityContext: + fsGroup: 471 + nodeSelector: + workload-type: apps + containers: + - name: taskmanager + image: {{ flink_image }} + args: ["taskmanager"] + env: + - name: FLINK_PROPERTIES + value: | + jobmanager.rpc.address: {{ flink_rpc }} + taskmanager.numberOfTaskSlots: {{ flink_task_slots }} + resources: + requests: + cpu: 250m + memory: 512Mi + limits: + cpu: 500m + memory: 1Gi \ No newline at end of file diff --git a/services/playbooks/roles/flink/templates/03-job.yml.j2 b/services/playbooks/roles/flink/templates/03-job.yml.j2 new file mode 100644 index 00000000..48cca5f1 --- /dev/null +++ b/services/playbooks/roles/flink/templates/03-job.yml.j2 @@ -0,0 +1,27 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: flink-job + labels: + app: flink-job +spec: + template: + spec: + nodeSelector: + workload-type: apps + restartPolicy: Never + containers: + - name: flink-job + image: {{ flink_job_image }} + imagePullPolicy: Always + env: + - name: FLINK_JM_HOST + value: "{{ flink_rpc }}" + - name: FLINK_JM_PORT + value: "{{ flink_jobmanager_port }}" + - name: FLINK_PARALLELISM + value: "{{ flink_job_parallelism | string }}" + - name: POSTGRES_URL + value: "{{ postgres_url }}" + - name: KAFKA_URL + value: "{{ kafka_url }}" diff --git a/services/playbooks/roles/flink/templates/04-jobservice.yml.j2 b/services/playbooks/roles/flink/templates/04-jobservice.yml.j2 new file mode 100644 index 00000000..3ef623ec --- /dev/null +++ b/services/playbooks/roles/flink/templates/04-jobservice.yml.j2 @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: flink-jobmanager +spec: + selector: + app: flink-jobmanager + ports: + - name: rpc + port: {{ flink_jobmanager_port }} + targetPort: {{ flink_jobmanager_port }} + - name: rpcport1 + port: {{ flink_rpc_port_1 }} + targetPort: {{ flink_rpc_port_1 }} + - name: rpcport2 + port: {{ flink_rpc_port_2 }} + targetPort: {{ flink_rpc_port_2 }} + type: ClusterIP diff --git a/services/playbooks/roles/grafana/defaults/main.yml b/services/playbooks/roles/grafana/defaults/main.yml new file mode 100644 index 00000000..7453eeff --- /dev/null +++ b/services/playbooks/roles/grafana/defaults/main.yml @@ -0,0 +1,19 @@ +--- +grafana_namespace: grafana +grafana_app_name: grafana +grafana_image: grafana/grafana:latest + +grafana_admin_user: admin +grafana_admin_password: admin123 + +grafana_db_type: postgres +grafana_db_host: tsdb-rw.data.svc.cluster.local:5432 +grafana_db_name: tracker_db +grafana_db_user: admin +grafana_db_password: administrator + +grafana_port: 3000 +grafana_service_ip: 10.20.30.56 + +grafana_storage_class: "longhorn" +grafana_storage_size: 1Gi diff --git a/services/playbooks/roles/grafana/tasks/main.yml b/services/playbooks/roles/grafana/tasks/main.yml new file mode 100644 index 00000000..45d9183f --- /dev/null +++ b/services/playbooks/roles/grafana/tasks/main.yml @@ -0,0 +1,24 @@ +- name: "Create Grafana Kubernetes namespace" + kubernetes.core.k8s: + kubeconfig: /etc/rancher/k3s/k3s.yaml + name: "{{ grafana_namespace }}" + api_version: v1 + kind: Namespace + state: present + +- name: "Create Grafana Persistent Volume Claim" + kubernetes.core.k8s: + kubeconfig: /etc/rancher/k3s/k3s.yaml + state: present + namespace: "{{ grafana_namespace }}" + definition: "{{ lookup('template', '03-pvc.yml.j2') | from_yaml }}" + +- name: "Deploy Grafana Deployment and Service" + kubernetes.core.k8s: + kubeconfig: /etc/rancher/k3s/k3s.yaml + state: present + namespace: "{{ grafana_namespace }}" + definition: "{{ lookup('template', item) | from_yaml }}" + loop: + - "01-deployment.yml.j2" + - "02-service.yml.j2" diff --git a/services/playbooks/roles/grafana/templates/01-deployment.yml.j2 b/services/playbooks/roles/grafana/templates/01-deployment.yml.j2 new file mode 100644 index 00000000..5ca30ed0 --- /dev/null +++ b/services/playbooks/roles/grafana/templates/01-deployment.yml.j2 @@ -0,0 +1,50 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ grafana_app_name }} + labels: + app: {{ grafana_app_name }} +spec: + nodeSelector: + workload-type: apps + replicas: 1 + selector: + matchLabels: + app: {{ grafana_app_name }} + template: + metadata: + labels: + app: {{ grafana_app_name }} + spec: + securityContext: + fsGroup: 472 + nodeSelector: + workload-type: apps + containers: + - name: {{ grafana_app_name }} + image: {{ grafana_image }} + imagePullPolicy: Always + ports: + - containerPort: {{ grafana_port }} + env: + - name: GF_SECURITY_ADMIN_USER + value: "{{ grafana_admin_user }}" + - name: GF_SECURITY_ADMIN_PASSWORD + value: "{{ grafana_admin_password }}" + - name: GF_DATABASE_TYPE + value: "{{ grafana_db_type }}" + - name: GF_DATABASE_HOST + value: "{{ grafana_db_host }}" + - name: GF_DATABASE_NAME + value: "{{ grafana_db_name }}" + - name: GF_DATABASE_USER + value: "{{ grafana_db_user }}" + - name: GF_DATABASE_PASSWORD + value: "{{ grafana_db_password }}" + volumeMounts: + - name: grafana-storage + mountPath: /var/lib/grafana + volumes: + - name: grafana-storage + persistentVolumeClaim: + claimName: grafana-pvc diff --git a/services/playbooks/roles/grafana/templates/02-service.yml.j2 b/services/playbooks/roles/grafana/templates/02-service.yml.j2 new file mode 100644 index 00000000..6e2e876f --- /dev/null +++ b/services/playbooks/roles/grafana/templates/02-service.yml.j2 @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ grafana_app_name }} +spec: + selector: + app: {{ grafana_app_name }} + ports: + - name: http + port: {{ grafana_port }} + targetPort: {{ grafana_port }} + type: LoadBalancer + loadBalancerIP: {{ grafana_service_ip }} diff --git a/services/playbooks/roles/grafana/templates/03-pvc.yml.j2 b/services/playbooks/roles/grafana/templates/03-pvc.yml.j2 new file mode 100644 index 00000000..cdd09b51 --- /dev/null +++ b/services/playbooks/roles/grafana/templates/03-pvc.yml.j2 @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: grafana-pvc +spec: + accessModes: + - ReadWriteOnce + storageClassName: {{ grafana_storage_class }} + resources: + requests: + storage: {{ grafana_storage_size }} diff --git a/services/playbooks/roles/kafka/defaults/main.yml b/services/playbooks/roles/kafka/defaults/main.yml new file mode 100644 index 00000000..06306c8c --- /dev/null +++ b/services/playbooks/roles/kafka/defaults/main.yml @@ -0,0 +1,11 @@ +kafka_namespace: data +zookeeper_image: confluentinc/cp-zookeeper:7.5.0 +kafka_image: confluentinc/cp-kafka:7.5.0 + +zookeeper_connect: zookeeper.data.svc.cluster.local:2181 +zookeeper_client_port: 2181 +zookeeper_tick_time: 2000 + +kafka_broker_id: 1 +kafka_port: 9092 +kafka_host_port: 29092 \ No newline at end of file diff --git a/services/playbooks/roles/kafka/tasks/main.yml b/services/playbooks/roles/kafka/tasks/main.yml new file mode 100644 index 00000000..c32eff5e --- /dev/null +++ b/services/playbooks/roles/kafka/tasks/main.yml @@ -0,0 +1,19 @@ +- name: "Create Kafka namespace" + kubernetes.core.k8s: + kubeconfig: /etc/rancher/k3s/k3s.yaml + name: "{{ kafka_namespace }}" + api_version: v1 + kind: Namespace + state: present + +- name: "Deploy Zookeeper and Kafka" + kubernetes.core.k8s: + kubeconfig: /etc/rancher/k3s/k3s.yaml + state: present + namespace: "{{ kafka_namespace }}" + definition: "{{ lookup('template', item) | from_yaml }}" + loop: + - "01-zookeeper-deployment.yml.j2" + - "02-zookeeper-service.yml.j2" + - "03-kafka-deployment.yml.j2" + - "04-kafka-service.yml.j2" diff --git a/services/playbooks/roles/kafka/templates/01-zookeeper-deployment.yml.j2 b/services/playbooks/roles/kafka/templates/01-zookeeper-deployment.yml.j2 new file mode 100644 index 00000000..8ac3ab24 --- /dev/null +++ b/services/playbooks/roles/kafka/templates/01-zookeeper-deployment.yml.j2 @@ -0,0 +1,28 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: zookeeper + labels: + app: zookeeper +spec: + replicas: 1 + selector: + matchLabels: + app: zookeeper + template: + metadata: + labels: + app: zookeeper + spec: + nodeSelector: + workload-type: apps + containers: + - name: zookeeper + image: {{ zookeeper_image }} + ports: + - containerPort: {{ zookeeper_client_port }} + env: + - name: ZOOKEEPER_CLIENT_PORT + value: "{{ zookeeper_client_port }}" + - name: ZOOKEEPER_TICK_TIME + value: "{{ zookeeper_tick_time }}" diff --git a/services/playbooks/roles/kafka/templates/02-zookeeper-service.yml.j2 b/services/playbooks/roles/kafka/templates/02-zookeeper-service.yml.j2 new file mode 100644 index 00000000..a936ee6f --- /dev/null +++ b/services/playbooks/roles/kafka/templates/02-zookeeper-service.yml.j2 @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: zookeeper +spec: + selector: + app: zookeeper + ports: + - name: client + port: {{ zookeeper_client_port }} + targetPort: {{ zookeeper_client_port }} + type: ClusterIP diff --git a/services/playbooks/roles/kafka/templates/03-kafka-deployment.yml.j2 b/services/playbooks/roles/kafka/templates/03-kafka-deployment.yml.j2 new file mode 100644 index 00000000..5bceea53 --- /dev/null +++ b/services/playbooks/roles/kafka/templates/03-kafka-deployment.yml.j2 @@ -0,0 +1,50 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kafka + labels: + app: kafka +spec: + replicas: 1 + selector: + matchLabels: + app: kafka + template: + metadata: + labels: + app: kafka + spec: + enableServiceLinks: false + nodeSelector: + workload-type: apps + containers: + - name: kafka + image: {{ kafka_image }} + ports: + - name: plaintext + containerPort: {{ kafka_port }} + - name: plaintext-host + containerPort: {{ kafka_host_port }} + env: + - name: KAFKA_BROKER_ID + value: "{{ kafka_broker_id }}" + - name: KAFKA_ZOOKEEPER_CONNECT + value: "{{ zookeeper_connect }}" + - name: KAFKA_ADVERTISED_LISTENERS + value: "PLAINTEXT://kafka.data.svc.cluster.local:{{ kafka_port }},PLAINTEXT_HOST://localhost:{{ kafka_host_port }}" + - name: KAFKA_LISTENER_SECURITY_PROTOCOL_MAP + value: "PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT" + - name: KAFKA_INTER_BROKER_LISTENER_NAME + value: "PLAINTEXT" + - name: KAFKA_LOG4J_ROOT_LOGLEVEL + value: "INFO" + readinessProbe: + tcpSocket: + port: {{ kafka_port }} + initialDelaySeconds: 30 + periodSeconds: 10 + livenessProbe: + tcpSocket: + port: {{ kafka_port }} + initialDelaySeconds: 60 + periodSeconds: 20 diff --git a/services/playbooks/roles/kafka/templates/04-kafka-service.yml.j2 b/services/playbooks/roles/kafka/templates/04-kafka-service.yml.j2 new file mode 100644 index 00000000..397ce636 --- /dev/null +++ b/services/playbooks/roles/kafka/templates/04-kafka-service.yml.j2 @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: kafka +spec: + selector: + app: kafka + ports: + - name: plaintext + port: {{ kafka_port }} + targetPort: {{ kafka_port }} + - name: plaintext-host + port: {{ kafka_host_port }} + targetPort: {{ kafka_host_port }} + type: ClusterIP diff --git a/services/playbooks/roles/mongo-express/defaults/main.yml b/services/playbooks/roles/mongo-express/defaults/main.yml new file mode 100644 index 00000000..74ef138f --- /dev/null +++ b/services/playbooks/roles/mongo-express/defaults/main.yml @@ -0,0 +1,6 @@ +--- +mongo_express_namespace: "management" +mongo_express_app_name: "mongo-express" +mongo_express_image: "mongo-express:latest" +mongo_express_server: mongo.data.svc.cluster.local +mongo_express_ip: 10.20.30.148 diff --git a/services/playbooks/roles/mongo-express/tasks/main.yml b/services/playbooks/roles/mongo-express/tasks/main.yml new file mode 100644 index 00000000..a74b821b --- /dev/null +++ b/services/playbooks/roles/mongo-express/tasks/main.yml @@ -0,0 +1,10 @@ +--- +- name: "Create Mongo Express Deployment and Service" + kubernetes.core.k8s: + kubeconfig: /etc/rancher/k3s/k3s.yaml + state: present + namespace: "{{ mongo_express_namespace }}" + definition: "{{ lookup('template', item) | from_yaml }}" + loop: + - "01-deployment.yml.j2" + - "02-service.yml.j2" diff --git a/services/playbooks/roles/mongo-express/templates/01-deployment.yml.j2 b/services/playbooks/roles/mongo-express/templates/01-deployment.yml.j2 new file mode 100644 index 00000000..5a3df62c --- /dev/null +++ b/services/playbooks/roles/mongo-express/templates/01-deployment.yml.j2 @@ -0,0 +1,25 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: "{{ mongo_express_app_name }}" + namespace: {{ mongo_express_namespace }} +spec: + replicas: 1 + selector: + matchLabels: + app: "{{ mongo_express_app_name }}" + template: + metadata: + labels: + app: "{{ mongo_express_app_name }}" + spec: + nodeSelector: + workload-type: apps + containers: + - name: "{{ mongo_express_app_name }}" + image: "{{ mongo_express_image }}" + env: + - name: ME_CONFIG_MONGODB_SERVER + value: "{{ mongo_express_server }}" + ports: + - containerPort: 8081 diff --git a/services/playbooks/roles/mongo-express/templates/02-service.yml.j2 b/services/playbooks/roles/mongo-express/templates/02-service.yml.j2 new file mode 100644 index 00000000..adf90509 --- /dev/null +++ b/services/playbooks/roles/mongo-express/templates/02-service.yml.j2 @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: "{{ mongo_express_app_name }}" + namespace: {{ mongo_express_namespace }} +spec: + type: LoadBalancer + loadBalancerIP: {{ mongo_express_ip }} + selector: + app: "{{ mongo_express_app_name }}" + ports: + - port: 8081 + targetPort: 8081 diff --git a/services/playbooks/roles/mongo/defaults/main.yml b/services/playbooks/roles/mongo/defaults/main.yml new file mode 100644 index 00000000..4ab3975f --- /dev/null +++ b/services/playbooks/roles/mongo/defaults/main.yml @@ -0,0 +1,9 @@ +--- +# defaults file for ansible-role-mongo +mongo_namespace: "data" +mongo_app_name: "mongo" +mongo_image: "mongo:latest" + +# Storage settings +mongo_pvc_storage_class: "longhorn" +mongo_pvc_storage_size: "3Gi" diff --git a/services/playbooks/roles/mongo/files/init.js b/services/playbooks/roles/mongo/files/init.js new file mode 100644 index 00000000..cc60bb57 --- /dev/null +++ b/services/playbooks/roles/mongo/files/init.js @@ -0,0 +1,855 @@ +db = db.getSiblingDB("trackeroo-backend"); +db.users.insertMany([ + { + username: "leonardo", + role: "admin", + password_hash: + "$2b$12$PHQyHC3QX7hSaSNk3otnJe90Htsf5nIqNeqSMKCWrmCHwbDvEUrwm", + last_login: new Date(), + session_token: "", + issued_at: new Date(), + expires_at: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours from now + }, + { + username: "simone", + role: "admin", + password_hash: + "$2b$12$CCBM/Xy54gPzHkJyMWovm.fTmUUqjg73GWb1cBDTKPaMU4JsfSXd6", + last_login: new Date(), + session_token: "", + issued_at: new Date(), + expires_at: new Date(Date.now() + 24 * 60 * 60 * 1000), + }, + { + username: "admin", + role: "admin", + password_hash: + "$2b$12$3CaXyt.SvoUeQiR5TAFdReV4AjnAlNe46/oL6SBd75souC5SvKLAu", + last_login: new Date(), + session_token: "", + issued_at: new Date(), + expires_at: new Date(Date.now() + 24 * 60 * 60 * 1000), + }, + { + username: "apps", + role: "admin", + password_hash: + "$2b$12$ZhKtxqqkFfrcEXiKjgvM0.BqM9sVXTEZAQCP5/qhRxMBZn2cWWbJ6", + last_login: new Date(), + session_token: "", + issued_at: new Date(), + expires_at: new Date(Date.now() + 24 * 60 * 60 * 1000), + }, + { + username: "trackeroo", + role: "admin", + password_hash: + "$2b$12$NAfj6rvgE20aWfxfK9Y0j.aVUaA1k3eJak2a0.u11nO0/9ijsFX4W", // trackeroo + last_login: new Date(), + session_token: "", + issued_at: new Date(), + expires_at: new Date(Date.now() + 24 * 60 * 60 * 1000), + }, +]); +db.devices.insertMany([ + { + _id: "trk-18608d12c8414acfcb9165b1", + name: "wonderful_wirth", + status: { connected: true, last_message: "2025-08-02T16:40:46Z" }, + device_type: "other", + private_key: "m9mFoEWSL+GwWtoyPZsF2q4HpwLddf7JfsxmedN8h+Y=", + created_at: ISODate("2025-08-18T03:36:46Z"), + }, + { + _id: "trk-18608d12c843cfc86452c8bc", + name: "xenodochial_carmack", + status: { connected: false, last_message: "2025-08-18T20:34:46Z" }, + device_type: "valuable", + private_key: "QNiUX1DdBq9hNGToa8ynDbXZRF9aDbO4LWvm7QspLo8=", + created_at: ISODate("2025-08-08T07:04:46Z"), + }, + { + _id: "trk-18608d12c8443834a667d6c2", + name: "merry_schwinger", + status: { connected: false, last_message: "2025-08-10T14:36:46Z" }, + device_type: "food", + private_key: "mS+C+G6Ut4mrbmz6v3EA5Fm7OvtcwrcRzvLOoIyIRDw=", + created_at: ISODate("2025-08-08T17:05:46Z"), + }, + { + _id: "trk-18608d12c8445101b3d3a3b8", + name: "thirsty_henry", + status: { connected: false, last_message: "2025-08-22T18:58:46Z" }, + device_type: "public_transport", + private_key: "4kKuUQSMJE9RckmbCFReQy5gtbRpY3sCgDjcTnxBxwI=", + created_at: ISODate("2025-08-04T05:03:46Z"), + }, + { + _id: "trk-18608d12c8446943ffbf2df9", + name: "admiring_bohr", + status: { connected: true, last_message: "2025-08-06T01:33:46Z" }, + device_type: "valuable", + private_key: "5mp9W9wUVd50eqGqC/JpW/0/pG6wBrAsivSPg0bcC2M=", + created_at: ISODate("2025-08-25T16:35:46Z"), + }, + { + _id: "trk-18608d12c84480bd0ad4656f", + name: "keen_bohr", + status: { connected: true, last_message: "2025-08-16T05:31:46Z" }, + device_type: "private_transport", + private_key: "/3tiwn6woUzwRGe/TUqOZQNGvYWHYSHj4d/Fpq9eTS8=", + created_at: ISODate("2025-08-01T14:09:46Z"), + }, + { + _id: "trk-18608d12c8449838a8eb28eb", + name: "clever_fermi", + status: { connected: true, last_message: "2025-08-08T23:13:46Z" }, + device_type: "valuable", + private_key: "pveqq6guV0EAQANRpMOQq5Yd8iSaZSH6QUMynO0ke6g=", + created_at: ISODate("2025-08-05T16:46:46Z"), + }, + { + _id: "trk-18608d12c844b01d0d668fce", + name: "brave_schrodinger", + status: { connected: false, last_message: "2025-08-04T16:28:46Z" }, + device_type: "valuable", + private_key: "+NrhtHayxZYPZnqA+C1C6uth3bORje/ka5uxXZ3E2zQ=", + created_at: ISODate("2025-08-04T20:21:46Z"), + }, + { + _id: "trk-18608d12c844cb262515236b", + name: "youthful_stallman", + status: { connected: false, last_message: "2025-08-22T21:24:46Z" }, + device_type: "valuable", + private_key: "dkvOCYpkWXr3WZs62jH9LfjOAbhm0TapSNQvcIzijLA=", + created_at: ISODate("2025-08-04T10:00:46Z"), + }, + { + _id: "trk-18608d12c844e58e640b9ea1", + name: "friendly_curie", + status: { connected: true, last_message: "2025-08-21T03:51:46Z" }, + device_type: "private_transport", + private_key: "1l0SNynykdBFZVNfGlkTYiniGlmk5KVgyW3bp8HI6xg=", + created_at: ISODate("2025-08-05T23:57:46Z"), + }, + { + _id: "trk-18608d12c844fccd26144714", + name: "charming_dirac", + status: { connected: false, last_message: "2025-07-31T23:08:46Z" }, + device_type: "public_transport", + private_key: "/Vi9D/RMiWilNw0jHA/fv4DbGwAEbqRAgQJulPl4DTQ=", + created_at: ISODate("2025-08-08T18:10:46Z"), + }, + { + _id: "trk-18608d12c8451452004ec96d", + name: "iron_franklin", + status: { connected: true, last_message: "2025-08-02T12:47:46Z" }, + device_type: "public_transport", + private_key: "V0exZwne86ptOGifjuWX7QmGjpN1W62szV6+WxX9m5s=", + created_at: ISODate("2025-08-07T12:55:46Z"), + }, + { + _id: "trk-18608d12c8456a397013ce6b", + name: "gentle_church", + status: { connected: false, last_message: "2025-08-11T23:09:46Z" }, + device_type: "other", + private_key: "KH+3BS3QnEec8vME0ToemUn3mK9i0bwCg1ZlvgnRfkU=", + created_at: ISODate("2025-08-15T20:23:46Z"), + }, + { + _id: "trk-18608d12c84581a0b80ca1c5", + name: "practical_michelson", + status: { connected: false, last_message: "2025-08-11T11:15:46Z" }, + device_type: "public_transport", + private_key: "ab3Wu7Glx9xhfSFUNRKzi1CZPfPeiCI7ujc/iJmYWpI=", + created_at: ISODate("2025-08-04T21:37:46Z"), + }, + { + _id: "trk-18608d12c845ad105b9a60db", + name: "phenomenal_born", + status: { connected: false, last_message: "2025-08-23T09:48:46Z" }, + device_type: "private_transport", + private_key: "h3dT1JuyA/MhH1/o/n/IiPRFEyyEvXlMl3ZNtrCHZwY=", + created_at: ISODate("2025-08-06T01:48:46Z"), + }, + { + _id: "trk-18608d12c845c8050d2973c1", + name: "agitated_planck", + status: { connected: false, last_message: "2025-08-20T06:43:46Z" }, + device_type: "food", + private_key: "l/TRQN08IpMfwVHCWHpcNQ/SBuRWLeNcuJZDf28697c=", + created_at: ISODate("2025-08-12T01:20:46Z"), + }, + { + _id: "trk-18608d12c845df659619c7e6", + name: "determined_kelvin", + status: { connected: true, last_message: "2025-08-16T14:01:46Z" }, + device_type: "valuable", + private_key: "MlxmbBIfcGNpReS6GUgLDmTRrDDQQ6rlLxj4Eggrfjo=", + created_at: ISODate("2025-08-10T07:50:46Z"), + }, + { + _id: "trk-18608d12c845f5d103e908ad", + name: "thoughtful_weber", + status: { connected: false, last_message: "2025-08-18T05:54:46Z" }, + device_type: "valuable", + private_key: "WGmlcdPBvesFWds8BZ4UR4k7ze/25P1GpWhXaSmgNDQ=", + created_at: ISODate("2025-08-04T22:08:46Z"), + }, + { + _id: "trk-18608d12c8460bf33167dbdc", + name: "hyper_shockley", + status: { connected: false, last_message: "2025-08-17T20:07:46Z" }, + device_type: "other", + private_key: "BzHUYgawXJvf458ZW6fLaKxbTEoOXBI1pgI4Z5ebMuM=", + created_at: ISODate("2025-08-17T07:01:46Z"), + }, + { + _id: "trk-18608d12c8462309d5e746cc", + name: "pious_planck", + status: { connected: true, last_message: "2025-08-12T19:59:46Z" }, + device_type: "private_transport", + private_key: "DMAF8lJbDW/hdBvKttHueeokLXgQ9nlcTQBDMfypO4w=", + created_at: ISODate("2025-08-15T02:45:46Z"), + }, + { + _id: "trk-18608d12c84640a54b9bd528", + name: "heuristic_fermi", + status: { connected: true, last_message: "2025-08-02T15:13:46Z" }, + device_type: "valuable", + private_key: "+crZGHsZ6r5ZN0UMsB0JFK/hveE6E267uzcAGdkQAZQ=", + created_at: ISODate("2025-08-09T02:32:46Z"), + }, + { + _id: "trk-18608d12c846577d4ec97cfb", + name: "sharp_edison", + status: { connected: true, last_message: "2025-08-14T03:13:46Z" }, + device_type: "private_transport", + private_key: "eW16LWeR4CcKm0mRqkBnv9lU6WYKXyzS8csOmoTwt+c=", + created_at: ISODate("2025-08-21T01:54:46Z"), + }, + { + _id: "trk-18608d12c8466e529b599dc4", + name: "romantic_hertz", + status: { connected: true, last_message: "2025-08-30T03:20:46Z" }, + device_type: "private_transport", + private_key: "kbRJ702ADyEPki+ko7MbOZ7AGvhKPSql5A7fK6MGFGI=", + created_at: ISODate("2025-08-17T20:13:46Z"), + }, + { + _id: "trk-18608d12c84685251cdaf35c", + name: "gallant_whitehead", + status: { connected: true, last_message: "2025-08-05T15:55:46Z" }, + device_type: "private_transport", + private_key: "ZeGHStsNcbp56gDEk+nasq/BaaLklfzXfuG8BcX+lrs=", + created_at: ISODate("2025-08-28T00:41:46Z"), + }, + { + _id: "trk-18608d12c8469c00840cc49b", + name: "laughing_rutherford", + status: { connected: false, last_message: "2025-08-13T10:56:46Z" }, + device_type: "private_transport", + private_key: "n0LNbNhaIaW42aBkqCwDM0cJSwcwL8McL8im5CP91v4=", + created_at: ISODate("2025-08-29T17:24:46Z"), + }, + { + _id: "trk-18608d12c846b3600123a417", + name: "sad_marconi", + status: { connected: true, last_message: "2025-08-05T23:52:46Z" }, + device_type: "food", + private_key: "g2D4mb6ezOrfK/ft41lACByyeU4IQVF6RWlB/0P7zhc=", + created_at: ISODate("2025-08-15T07:38:46Z"), + }, + { + _id: "trk-18608d12c846ca7bab489e30", + name: "clever_newton", + status: { connected: true, last_message: "2025-08-18T06:39:46Z" }, + device_type: "valuable", + private_key: "hGz/4/osEsLT/5Q53TTlID+wA54Xb2p8Cxpj05jZsik=", + created_at: ISODate("2025-08-18T00:25:46Z"), + }, + { + _id: "trk-18608d12c846e0a3e38974e5", + name: "dazzling_maxwell", + status: { connected: false, last_message: "2025-08-10T04:46:46Z" }, + device_type: "other", + private_key: "KSG9WiZ6pWINfm9FH9vS/GSo3t1TmLf+eE5eGhU+Hik=", + created_at: ISODate("2025-08-05T06:51:46Z"), + }, + { + _id: "trk-18608d12c847064ce1bdf7ee", + name: "elegant_lagrange", + status: { connected: true, last_message: "2025-08-28T18:21:46Z" }, + device_type: "public_transport", + private_key: "tlrnwpslaBwOChP+dAFPQ2sfItsoKERU+MR33NHk9yQ=", + created_at: ISODate("2025-08-19T09:04:46Z"), + }, + { + _id: "trk-18608d12c8472b9f0905b3ad", + name: "ecstatic_riemann", + status: { connected: true, last_message: "2025-08-05T19:27:46Z" }, + device_type: "private_transport", + private_key: "5v94oQP8pNQZrOZrYNTTl5c67qyrPDUJE4eZTfuBjVU=", + created_at: ISODate("2025-08-08T11:59:46Z"), + }, + { + _id: "trk-18608d12c84742df27b88f6b", + name: "cranky_ampere", + status: { connected: false, last_message: "2025-08-19T19:18:46Z" }, + device_type: "other", + private_key: "cS34G/an+Z+MM1M5P5dh48B+y4kjyy0eI/8cw4ClkT4=", + created_at: ISODate("2025-08-08T18:36:46Z"), + }, + { + _id: "trk-18608d12c84759c8a9ef04f8", + name: "energetic_hilbert", + status: { connected: true, last_message: "2025-08-25T05:04:46Z" }, + device_type: "public_transport", + private_key: "EInJWD1jh2t06pz3D5WZjspYvxzqs57NRKAxVhe9uIk=", + created_at: ISODate("2025-08-28T01:57:46Z"), + }, + { + _id: "trk-18608d12c84770f5153b97ef", + name: "amazing_turing", + status: { connected: false, last_message: "2025-08-10T10:51:46Z" }, + device_type: "other", + private_key: "X9Lx6+KllLFtNDyvj/466CrOQmF6UewS+3xs9j18ILA=", + created_at: ISODate("2025-08-03T11:49:46Z"), + }, + { + _id: "trk-18608d12c84786f21e4edc5e", + name: "happy_einstein", + status: { connected: true, last_message: "2025-08-21T12:01:46Z" }, + device_type: "private_transport", + private_key: "wzTOvpERsSN6QO4jgUD3XuRtcdqQLItdjSxSgrKmcoo=", + created_at: ISODate("2025-08-17T21:31:46Z"), + }, + { + _id: "trk-18608d12c8479d828433ff9a", + name: "funny_peano", + status: { connected: true, last_message: "2025-08-11T05:13:46Z" }, + device_type: "private_transport", + private_key: "cqseF4l1F5lAgCe7bD6EP+rEE+L1wFhc+10MQV48UJo=", + created_at: ISODate("2025-08-24T04:47:46Z"), + }, + { + _id: "trk-18608d12c847b3e74258eb03", + name: "vibrant_thompson", + status: { connected: false, last_message: "2025-08-15T07:02:46Z" }, + device_type: "private_transport", + private_key: "O0XXGhe7rp/EsEYyXNAGIKjJk5T+FXCAdebGqh/dluo=", + created_at: ISODate("2025-08-16T09:54:46Z"), + }, + { + _id: "trk-18608d12c8480b18fe88b4af", + name: "patient_wu", + status: { connected: false, last_message: "2025-08-21T22:36:46Z" }, + device_type: "public_transport", + private_key: "bHzGkyOqJLG9GAnkC25B2OLoXhQhIUu3bPVIE9sBlzU=", + created_at: ISODate("2025-08-24T18:56:46Z"), + }, + { + _id: "trk-18608d12c84823aa3e06b7da", + name: "epic_cantor", + status: { connected: true, last_message: "2025-08-07T10:15:46Z" }, + device_type: "private_transport", + private_key: "G7MRIMBP24R0xdEXZRIV3me9dDDFRdxtTXuLXe28494=", + created_at: ISODate("2025-08-02T10:17:46Z"), + }, + { + _id: "trk-18608d12c8483aeb4dce2724", + name: "hopeful_bardeen", + status: { connected: true, last_message: "2025-08-30T06:16:46Z" }, + device_type: "public_transport", + private_key: "Xd5a7XzvWCVjDtduORbuAH1sykFtGHcvAhK/4BadcaI=", + created_at: ISODate("2025-08-06T23:01:46Z"), + }, + { + _id: "trk-18608d12c8486259917b5535", + name: "frosty_dedekind", + status: { connected: false, last_message: "2025-08-07T10:35:46Z" }, + device_type: "private_transport", + private_key: "I0ahyNHzwY2ax1RTIwkdAW1+4eErU6s1WISzCED2ImI=", + created_at: ISODate("2025-08-05T02:16:46Z"), + }, + { + _id: "trk-18608d12c8487a58b5c5651d", + name: "heartwarming_bose", + status: { connected: true, last_message: "2025-08-15T11:05:46Z" }, + device_type: "other", + private_key: "LsgbCoMQG+opSUt9JrsiDIL4Z1k5VU+WnTGajUsfRMU=", + created_at: ISODate("2025-08-04T01:01:46Z"), + }, + { + _id: "trk-18608d12c848919ae0a5ac8f", + name: "kind_torvalds", + status: { connected: false, last_message: "2025-08-02T19:55:46Z" }, + device_type: "food", + private_key: "ij0mk0VXVo7nsb0cqpGJX0V5oChhDa830+CJ72ZW8xk=", + created_at: ISODate("2025-08-20T23:21:46Z"), + }, + { + _id: "trk-18608d12c848b620cb14aa25", + name: "fascinated_noether", + status: { connected: false, last_message: "2025-08-20T21:05:46Z" }, + device_type: "other", + private_key: "XJVLbd6UO12QkuIGo3EbZGXjG4xPKSvliiOLRLnMZmI=", + created_at: ISODate("2025-08-09T07:11:46Z"), + }, + { + _id: "trk-18608d12c848d48480a5d5b6", + name: "puzzled_fizeau", + status: { connected: true, last_message: "2025-08-22T09:43:46Z" }, + device_type: "food", + private_key: "ceSr36+tty8/YgIQuT7V66kMAXhi+K2JPN7EqEF7r3Y=", + created_at: ISODate("2025-08-02T11:41:46Z"), + }, + { + _id: "trk-18608d12c848eb34f8760e19", + name: "competent_rutherford", + status: { connected: true, last_message: "2025-08-04T03:29:46Z" }, + device_type: "valuable", + private_key: "e8kkc8+QotgP718tO8zL2+ukDwUiNfSmRl3UQFKnEwc=", + created_at: ISODate("2025-08-02T23:24:46Z"), + }, + { + _id: "trk-18608d12c8490334a6303366", + name: "pedantic_pauli", + status: { connected: true, last_message: "2025-08-30T09:45:46Z" }, + device_type: "private_transport", + private_key: "kXVOIOGZGLfPzYCPK55aVbR15kbwTZr9ezA6zSxHEig=", + created_at: ISODate("2025-08-05T01:38:46Z"), + }, + { + _id: "trk-18608d12c8491a91b1f888a9", + name: "magical_dirac", + status: { connected: false, last_message: "2025-08-08T15:21:46Z" }, + device_type: "valuable", + private_key: "C9biy8JKSu1nQCKQxGTjuxkjHr4W46086h3KwjVBr/I=", + created_at: ISODate("2025-08-05T22:46:46Z"), + }, + { + _id: "trk-18608d12c84931d6fa2d6232", + name: "serene_tesla", + status: { connected: false, last_message: "2025-08-03T19:17:46Z" }, + device_type: "private_transport", + private_key: "xPGoSwT+dTYd6ZX1++0+CJC8iEwVscx3VoDDI+LXWw8=", + created_at: ISODate("2025-08-05T07:06:46Z"), + }, + { + _id: "trk-18608d12c8494897112ba78e", + name: "peaceful_pascal", + status: { connected: true, last_message: "2025-08-05T08:37:46Z" }, + device_type: "other", + private_key: "WAVVSnHd8206VRwJOtgHDuUGDf0orqqYBIqodJA9L1I=", + created_at: ISODate("2025-08-15T05:44:46Z"), + }, + { + _id: "trk-18608d12c8495fd71857ce3f", + name: "stoic_ohm", + status: { connected: false, last_message: "2025-08-22T12:36:46Z" }, + device_type: "food", + private_key: "vzDwSMp/L6oyVEyYj3RKTY9UKyBj3V7LvhCD6cqe/nI=", + created_at: ISODate("2025-08-22T20:43:46Z"), + }, + { + _id: "trk-18608d12c84976fefd57d1ff", + name: "fearless_galois", + status: { connected: false, last_message: "2025-08-10T16:04:46Z" }, + device_type: "private_transport", + private_key: "PoHtJ2l8pTjJPJctSMrIII20agqTIk4LpVoiIdNLzgo=", + created_at: ISODate("2025-08-03T14:47:46Z"), + }, + { + _id: "trk-18608d12c8498d777941e066", + name: "distracted_planck", + status: { connected: true, last_message: "2025-08-24T07:49:46Z" }, + device_type: "food", + private_key: "W8F6g6HmjQAP3lwJjRqAfdQOIOsSq1vzEVbHMS8ZUAo=", + created_at: ISODate("2025-08-21T20:41:46Z"), + }, + { + _id: "trk-18608d12c849a50c845c25a9", + name: "optimistic_babbage", + status: { connected: true, last_message: "2025-08-12T09:06:46Z" }, + device_type: "valuable", + private_key: "zpX14uHmth5/ZzzNaL68qeoHLJRZjT3huaYns0LmnWU=", + created_at: ISODate("2025-08-06T17:19:46Z"), + }, + { + _id: "trk-18608d12c849bd6d485879c1", + name: "tender_faraday", + status: { connected: true, last_message: "2025-08-10T12:57:46Z" }, + device_type: "public_transport", + private_key: "O8XxluA24EC4bsgPrBHWCwS8q8GmBK7mv0byordbYc8=", + created_at: ISODate("2025-08-13T13:09:46Z"), + }, + { + _id: "trk-18608d12c849d409351613c2", + name: "jaunty_pauling", + status: { connected: true, last_message: "2025-08-27T07:44:46Z" }, + device_type: "public_transport", + private_key: "OLbAlVzxkhzr2wlaO/WV8fCB/m4jJQ5AIfrJ0tUgF0A=", + created_at: ISODate("2025-08-20T22:21:46Z"), + }, + { + _id: "trk-18608d12c84a2e54a6ffdd76", + name: "suspicious_volta", + status: { connected: true, last_message: "2025-08-03T18:04:46Z" }, + device_type: "valuable", + private_key: "S30Z2mlkvsUR6yH8nULY8RqPhmUcxEHEcEVuP7ePmSM=", + created_at: ISODate("2025-08-10T10:53:46Z"), + }, + { + _id: "trk-18608d12c84a5f766256c9e2", + name: "serene_hopper", + status: { connected: true, last_message: "2025-08-13T10:31:46Z" }, + device_type: "food", + private_key: "DTOj/Y2DiO0Ju5vwxOUgBtwXB5579pz30EEMYun7z00=", + created_at: ISODate("2025-08-18T16:45:46Z"), + }, + { + _id: "trk-18608d12c84a892c3aad3955", + name: "eager_gauss", + status: { connected: false, last_message: "2025-08-06T19:07:46Z" }, + device_type: "public_transport", + private_key: "Dl4fLpzCBXqNHjtrgm+5jrj+tSXPZRxttltQPvr/JcY=", + created_at: ISODate("2025-08-29T10:30:46Z"), + }, + { + _id: "trk-18608d12c84aa049b09a2169", + name: "eager_darwin", + status: { connected: true, last_message: "2025-08-10T19:58:46Z" }, + device_type: "other", + private_key: "EC7AoSY05EyPbRVEl/eGTSfESGXZZ4OWF5FsPcaKdTU=", + created_at: ISODate("2025-08-24T18:39:46Z"), + }, + { + _id: "trk-18608d12c84ab6d05b8082ad", + name: "hardcore_bell", + status: { connected: true, last_message: "2025-08-25T03:22:46Z" }, + device_type: "food", + private_key: "Ar3WbxWHU8l2uyOZPnqJffGQ9vJpXAovM/ZoNRYd9RA=", + created_at: ISODate("2025-08-20T22:33:46Z"), + }, + { + _id: "trk-18608d12c84acd0d1c7fc74a", + name: "enchanting_poincare", + status: { connected: false, last_message: "2025-08-12T07:20:46Z" }, + device_type: "private_transport", + private_key: "0HbmI0I5esubDm4b6XvzgLgHJtXVxGTeIt4EiHJAKc0=", + created_at: ISODate("2025-08-08T23:41:46Z"), + }, + { + _id: "trk-18608d12c84ae3dffff2e52d", + name: "proud_morley", + status: { connected: true, last_message: "2025-08-17T10:23:46Z" }, + device_type: "private_transport", + private_key: "NYrTqrlsGqPo9r6nKF2+bVd+QshvHb00Z676LpK3R00=", + created_at: ISODate("2025-08-30T11:59:46Z"), + }, + { + _id: "trk-18608d12c84afa2c10aa6c55", + name: "inspiring_bardeen", + status: { connected: false, last_message: "2025-08-15T18:29:46Z" }, + device_type: "other", + private_key: "6dU2H5RdD5VaZyXi5ZWaj5rkydlEV5f2PS1l9+fvL+c=", + created_at: ISODate("2025-08-07T16:21:46Z"), + }, + { + _id: "trk-18608d12c84b11c367999861", + name: "objective_glashow", + status: { connected: true, last_message: "2025-08-26T14:04:46Z" }, + device_type: "private_transport", + private_key: "uW2kufM6EeR+tpJHDYRmd/5CPGLMn84jMBsu6mhWtRU=", + created_at: ISODate("2025-08-19T03:27:46Z"), + }, + { + _id: "trk-18608d12c84b2feca3792861", + name: "modest_dyson", + status: { connected: false, last_message: "2025-08-25T10:25:46Z" }, + device_type: "public_transport", + private_key: "5XYG/w/clJolar2hAO3n4D9+fJdckv7i0ZO0S2m2KRo=", + created_at: ISODate("2025-08-02T00:09:46Z"), + }, + { + _id: "trk-18608d12c84b469627a640d9", + name: "boring_heisenberg", + status: { connected: false, last_message: "2025-08-25T03:19:46Z" }, + device_type: "food", + private_key: "EHPvNh8KnmQNbD8Kef2UZh+07Y6DiHKpNwTuuIcyemo=", + created_at: ISODate("2025-08-10T16:52:46Z"), + }, + { + _id: "trk-18608d12c84b5cae4cff3133", + name: "strange_ampere", + status: { connected: true, last_message: "2025-08-28T04:31:46Z" }, + device_type: "private_transport", + private_key: "fUqc7PB2fR3likQIUigerur96bUzz7dQZE/lMUiMJHY=", + created_at: ISODate("2025-08-08T07:02:46Z"), + }, + { + _id: "trk-18608d12c84b74702fe3e85c", + name: "faithful_hardy", + status: { connected: false, last_message: "2025-08-27T20:37:46Z" }, + device_type: "private_transport", + private_key: "etflzQEpsxaJqAlO2o/G45T+s80cWWaSV0KPobuWWFE=", + created_at: ISODate("2025-08-02T15:50:46Z"), + }, + { + _id: "trk-18608d12c84b8b83746dbc8b", + name: "eloquent_leibniz", + status: { connected: true, last_message: "2025-08-14T14:03:46Z" }, + device_type: "food", + private_key: "nwdfO4d3o5nmJsO/i8xQrgcjsa2KNSQQ9PQzgMNbEc8=", + created_at: ISODate("2025-08-13T23:17:46Z"), + }, + { + _id: "trk-18608d12c84ba2911c4a675e", + name: "cool_ohm", + status: { connected: false, last_message: "2025-08-12T06:22:46Z" }, + device_type: "other", + private_key: "7TXa0utsnnJXx7tHWYt5KC8nwv1xThn8iy12KI2zOvE=", + created_at: ISODate("2025-08-12T01:49:46Z"), + }, + { + _id: "trk-18608d12c84bde9e8b791f18", + name: "fervent_abel", + status: { connected: true, last_message: "2025-08-03T23:36:46Z" }, + device_type: "other", + private_key: "1doRBvIJsrXQIG1Ui9albOMUHxMjtrjU6VvNLZTZQak=", + created_at: ISODate("2025-08-29T22:05:46Z"), + }, + { + _id: "trk-18608d12c84bf63dcc1fb3e7", + name: "groovy_shannon", + status: { connected: true, last_message: "2025-08-10T16:58:46Z" }, + device_type: "food", + private_key: "4NnTHCSG64xQf807OjIJlzc+MdgrOv9/a83gDgRzUwQ=", + created_at: ISODate("2025-08-16T23:38:46Z"), + }, + { + _id: "trk-18608d12c84c23101fa0a8b6", + name: "grieving_wiener", + status: { connected: true, last_message: "2025-08-14T09:38:46Z" }, + device_type: "food", + private_key: "BpChhURWcEyErTfXhnx6J9gg4L+Qy4FyWzdeL6e27u4=", + created_at: ISODate("2025-08-14T21:20:46Z"), + }, + { + _id: "trk-18608d12c84c3ae0fd8aad7e", + name: "optimized_higgs", + status: { connected: false, last_message: "2025-08-21T14:05:46Z" }, + device_type: "other", + private_key: "csXqVuqw2AXm2A46aCt97ytsUzs5pA6t5Jnit25JFkI=", + created_at: ISODate("2025-08-15T19:27:46Z"), + }, + { + _id: "trk-18608d12c84c5151bb792931", + name: "boring_wozniak", + status: { connected: true, last_message: "2025-08-08T02:27:46Z" }, + device_type: "food", + private_key: "X3CHEXehWzXvRfQAJ6GGCoeEbVPcdX95JCzT3uEBYSs=", + created_at: ISODate("2025-08-02T10:22:46Z"), + }, + { + _id: "trk-18608d12c84c678173241105", + name: "curious_feynman", + status: { connected: false, last_message: "2025-08-21T18:29:46Z" }, + device_type: "public_transport", + private_key: "XxN0HpCbfzUE2AMRPcteDPSRLT5YgnAy4ILOxwIzm/g=", + created_at: ISODate("2025-08-26T19:07:46Z"), + }, + { + _id: "trk-18608d12c84c7e6385d059d6", + name: "noble_weinberg", + status: { connected: true, last_message: "2025-08-07T01:06:46Z" }, + device_type: "valuable", + private_key: "SZzpdjeGSaaWEBBa8apWtQJdwscbjyJ4jC/nXgnxJu4=", + created_at: ISODate("2025-08-21T16:34:46Z"), + }, + { + _id: "trk-18608d12c84c9470140d32f6", + name: "upbeat_ritchie", + status: { connected: false, last_message: "2025-08-10T09:04:46Z" }, + device_type: "private_transport", + private_key: "65mhzIpZvOx51yE6nPxQgjgy3rwlv13lMSM5X7vEFeQ=", + created_at: ISODate("2025-08-01T12:55:46Z"), + }, + { + _id: "trk-18608d12c84caba1375500b4", + name: "quizzical_doppler", + status: { connected: true, last_message: "2025-08-11T14:42:46Z" }, + device_type: "food", + private_key: "t/9N9FYcN12suJ/nZl5q6jC7OAWv0h2Pow6vJBSr4dA=", + created_at: ISODate("2025-08-29T00:09:46Z"), + }, + { + _id: "trk-18608d12c84cc3de436f7bde", + name: "silly_westinghouse", + status: { connected: true, last_message: "2025-08-28T04:22:46Z" }, + device_type: "food", + private_key: "Ba4MVIb1YQL6dm2eYRiJOX0CljrdeuFtAI2HZ3nBtsE=", + created_at: ISODate("2025-08-02T07:07:46Z"), + }, + { + _id: "trk-18608d12c84cdbbc4c9327d2", + name: "dreamy_tesla", + status: { connected: true, last_message: "2025-08-12T22:50:46Z" }, + device_type: "other", + private_key: "/kDru3xkiT7oAfKFD4Mp6nVl5IIFEMXponchC0ojKlo=", + created_at: ISODate("2025-08-01T00:21:46Z"), + }, + { + _id: "trk-18608d12c84cf3df4741680f", + name: "jovial_mendeleev", + status: { connected: true, last_message: "2025-08-06T08:10:46Z" }, + device_type: "valuable", + private_key: "voqhD27X8L+VW4e/Op93AyazBBx/a7mqhTbAsvn6tnc=", + created_at: ISODate("2025-08-26T23:38:46Z"), + }, + { + _id: "trk-18608d12c84d0b5fc332f041", + name: "great_kolmogorov", + status: { connected: false, last_message: "2025-08-14T09:59:46Z" }, + device_type: "valuable", + private_key: "TH/tO2mmlgP3dnH6h+ickwu8IaEajnm+DpvDLcAZDC8=", + created_at: ISODate("2025-08-29T11:27:46Z"), + }, + { + _id: "trk-18608d12c84d2247a477b6ff", + name: "interesting_watson", + status: { connected: true, last_message: "2025-08-20T09:29:46Z" }, + device_type: "other", + private_key: "ZBNn287pTaxPwGShBOOICVcZDmQbcdr2vtmLYbelgoU=", + created_at: ISODate("2025-08-19T19:46:46Z"), + }, + { + _id: "trk-18608d12c84d385e1d5c7bbc", + name: "zealous_berners", + status: { connected: true, last_message: "2025-08-04T11:23:46Z" }, + device_type: "private_transport", + private_key: "5Wx/7819jHalM4yOsTrhtswnTS8ItR5/n0BEaYFotQM=", + created_at: ISODate("2025-08-18T18:58:46Z"), + }, + { + _id: "trk-18608d12c84d62f4adb3c923", + name: "condescending_volta", + status: { connected: true, last_message: "2025-08-15T00:32:46Z" }, + device_type: "food", + private_key: "CKwOOv0LRsGpDURy9iXE9tnUFdjtI6ZqtAFoJkal360=", + created_at: ISODate("2025-08-26T02:07:46Z"), + }, + { + _id: "trk-18608d12c84d9ce75be19956", + name: "adoring_morse", + status: { connected: false, last_message: "2025-08-28T04:09:46Z" }, + device_type: "private_transport", + private_key: "iL0T7kaXpwPrO6Z0mA7Kk2Gs+tHEBl6fNSbFV3rSgVM=", + created_at: ISODate("2025-08-04T14:20:46Z"), + }, + { + _id: "trk-18608d12c84db5aa45ada38e", + name: "elastic_fourier", + status: { connected: true, last_message: "2025-08-22T06:00:46Z" }, + device_type: "public_transport", + private_key: "8loHVYewF31YJGSlkcOrMZpfc5eQ2DFhuvpne8M68Xc=", + created_at: ISODate("2025-08-09T06:44:46Z"), + }, + { + _id: "trk-18608d12c84dccaa3ac843bd", + name: "thrilled_gauss", + status: { connected: false, last_message: "2025-08-14T07:07:46Z" }, + device_type: "public_transport", + private_key: "jhESuO3AwXCtX0TcH5E9l91DDRUUx3SIErqpjt+SpUk=", + created_at: ISODate("2025-08-25T19:51:46Z"), + }, + { + _id: "trk-18608d12c84de3a8df5efbcc", + name: "goofy_markov", + status: { connected: false, last_message: "2025-08-20T02:12:46Z" }, + device_type: "public_transport", + private_key: "1bmj0Qvq4D9EsyHZJpITpEqEBBrOcD9KsiUhDX4X7R4=", + created_at: ISODate("2025-08-27T19:33:46Z"), + }, + { + _id: "trk-18608d12c84dfa6590d8fbda", + name: "relaxed_turing", + status: { connected: true, last_message: "2025-08-29T02:47:46Z" }, + device_type: "private_transport", + private_key: "bMvQyF6Lu/h5zYPEn4TBAOAxjVj67QTXl3ifZYf3S7s=", + created_at: ISODate("2025-08-09T23:16:46Z"), + }, + { + _id: "trk-18608d12c84e12b161fb52f1", + name: "exciting_godel", + status: { connected: true, last_message: "2025-08-19T23:11:46Z" }, + device_type: "valuable", + private_key: "L+ZecOMBWt0L7Ix8sPW8MZpDskk30MZydLOV1av++fk=", + created_at: ISODate("2025-08-30T12:59:46Z"), + }, + { + _id: "trk-18608d12c84e2a4a52da79d9", + name: "sweet_galvani", + status: { connected: false, last_message: "2025-08-29T21:21:46Z" }, + device_type: "valuable", + private_key: "dDVOk5b+i7+npM3o7icJocZSNFaed64RKX6HuxK5TTk=", + created_at: ISODate("2025-08-22T08:08:46Z"), + }, + { + _id: "trk-18608d12c84e473a6d897698", + name: "fabulous_ramanujan", + status: { connected: false, last_message: "2025-08-18T06:15:46Z" }, + device_type: "private_transport", + private_key: "n/7uqrA1Xh7uzYaX4BouodyLFlU/HWXMF3Gjd5gRN6g=", + created_at: ISODate("2025-08-19T16:05:46Z"), + }, + { + _id: "trk-18608d12c84e67b817e2a8fd", + name: "trusting_knuth", + status: { connected: true, last_message: "2025-08-28T09:04:46Z" }, + device_type: "private_transport", + private_key: "frrz+vzXLVQV4HHhZf7MFMPo4iGIJQWsgSPIkHKJ8OE=", + created_at: ISODate("2025-08-01T15:20:46Z"), + }, + { + _id: "trk-18608d12c84e7f725b9dd529", + name: "quirky_shannon", + status: { connected: false, last_message: "2025-08-16T17:49:46Z" }, + device_type: "food", + private_key: "443NJMtr5p+i0QIA8th2B9QTsh3aKhI1p51G0uwN1iQ=", + created_at: ISODate("2025-08-09T21:36:46Z"), + }, + { + _id: "trk-18608d12c84e95effa9dc925", + name: "nervous_hawking", + status: { connected: true, last_message: "2025-08-15T23:40:46Z" }, + device_type: "food", + private_key: "XTAux5J/tThXxN+5AoXR61Pxm0nfvJ92SOpCVo9HVpk=", + created_at: ISODate("2025-08-18T09:24:46Z"), + }, + { + _id: "trk-18608d12c84eaef489342f7e", + name: "exotic_turing", + status: { connected: true, last_message: "2025-08-03T15:10:46Z" }, + device_type: "public_transport", + private_key: "rrNb0cdFEBeDwGfwZXv8U1VqHz4QfxT6Re+o+HR8vGA=", + created_at: ISODate("2025-08-16T03:12:46Z"), + }, + { + _id: "trk-18608d12c84ec5eea3a8dae1", + name: "gifted_kleene", + status: { connected: true, last_message: "2025-08-12T00:58:46Z" }, + device_type: "valuable", + private_key: "EaBPc3d6Edkfi3YYxsqc+sAMILGwn2RbsIkB4zmnSU0=", + created_at: ISODate("2025-08-24T06:35:46Z"), + }, + { + _id: "trk-18608d12c84ee5a2e49c6f83", + name: "lucid_heisenberg", + status: { connected: true, last_message: "2025-08-08T11:32:46Z" }, + device_type: "valuable", + private_key: "Rb/XilBAyjcoGfgM4JkwZbZNEZsdf5pNWUXtF9/NqR8=", + created_at: ISODate("2025-08-19T05:39:46Z"), + }, +]); diff --git a/services/playbooks/roles/mongo/tasks/main.yml b/services/playbooks/roles/mongo/tasks/main.yml new file mode 100644 index 00000000..8fb08599 --- /dev/null +++ b/services/playbooks/roles/mongo/tasks/main.yml @@ -0,0 +1,23 @@ +--- +- name: "Create MongoDB ConfigMap from init.js file" + kubernetes.core.k8s: + kubeconfig: /etc/rancher/k3s/k3s.yaml + state: present + namespace: "{{ mongo_namespace }}" + definition: + apiVersion: v1 + kind: ConfigMap + metadata: + name: "{{ mongo_app_name }}-init-script" + data: + init.js: "{{ lookup('file', 'init.js') }}" + +- name: "Create MongoDB StatefulSet and Service" + kubernetes.core.k8s: + kubeconfig: /etc/rancher/k3s/k3s.yaml + state: present + namespace: "{{ mongo_namespace }}" + definition: "{{ lookup('template', item) | from_yaml }}" + loop: + - "01-statefulset.yml.j2" + - "02-service.yml.j2" diff --git a/services/playbooks/roles/mongo/templates/01-statefulset.yml.j2 b/services/playbooks/roles/mongo/templates/01-statefulset.yml.j2 new file mode 100644 index 00000000..1c646a6b --- /dev/null +++ b/services/playbooks/roles/mongo/templates/01-statefulset.yml.j2 @@ -0,0 +1,51 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: "{{ mongo_app_name }}" + namespace: "{{ mongo_namespace }}" +spec: + serviceName: "{{ mongo_app_name }}" + replicas: 1 + selector: + matchLabels: + app: "{{ mongo_app_name }}" + template: + metadata: + labels: + app: "{{ mongo_app_name }}" + spec: + nodeSelector: + workload-type: apps + containers: + - name: "{{ mongo_app_name }}" + image: "{{ mongo_image }}" + ports: + - containerPort: 27017 + name: mongo + readinessProbe: + exec: + command: ["mongosh", "--eval", "db.adminCommand('ping')"] + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + failureThreshold: 5 + successThreshold: 1 + volumeMounts: + - name: mongo-data-volume + mountPath: /data/db + - name: init-script-volume + mountPath: /docker-entrypoint-initdb.d + volumes: + - name: init-script-volume + configMap: + name: "{{ mongo_app_name }}-init-script" + volumeClaimTemplates: + - metadata: + name: mongo-data-volume + spec: + accessModes: ["ReadWriteOnce"] + storageClassName: "{{ mongo_pvc_storage_class }}" + resources: + requests: + storage: "{{ mongo_pvc_storage_size }}" diff --git a/services/playbooks/roles/mongo/templates/02-service.yml.j2 b/services/playbooks/roles/mongo/templates/02-service.yml.j2 new file mode 100644 index 00000000..8b2de27e --- /dev/null +++ b/services/playbooks/roles/mongo/templates/02-service.yml.j2 @@ -0,0 +1,14 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: "{{ mongo_app_name }}" + namespace: "{{ mongo_namespace }}" +spec: + type: ClusterIP + selector: + app: "{{ mongo_app_name }}" + ports: + - protocol: TCP + port: 27017 + targetPort: 27017 diff --git a/services/playbooks/roles/pgadmin/defaults/main.yml b/services/playbooks/roles/pgadmin/defaults/main.yml index 79fc4f82..50be7944 100644 --- a/services/playbooks/roles/pgadmin/defaults/main.yml +++ b/services/playbooks/roles/pgadmin/defaults/main.yml @@ -1,5 +1,5 @@ pgadmin: email: admin@sdc.unipi.it password: admin - namespace: data + namespace: management service_ip: 10.20.30.149 diff --git a/services/playbooks/roles/pgadmin/templates/pgadmin-deployment.yml.j2 b/services/playbooks/roles/pgadmin/templates/pgadmin-deployment.yml.j2 index b7361020..d7c0376b 100644 --- a/services/playbooks/roles/pgadmin/templates/pgadmin-deployment.yml.j2 +++ b/services/playbooks/roles/pgadmin/templates/pgadmin-deployment.yml.j2 @@ -18,6 +18,8 @@ spec: labels: app: pgadmin spec: + nodeSelector: + workload-type: apps containers: - name: pgadmin image: dpage/pgadmin4 diff --git a/services/playbooks/roles/rabbitmq/defaults/main.yml b/services/playbooks/roles/rabbitmq/defaults/main.yml index e69de29b..6dd538ef 100644 --- a/services/playbooks/roles/rabbitmq/defaults/main.yml +++ b/services/playbooks/roles/rabbitmq/defaults/main.yml @@ -0,0 +1,21 @@ +--- +# Kubernetes settings +rabbitmq_namespace: "trackeroo" +rabbitmq_app_name: "rabbitmq" +rabbitmq_replicas: 3 +rabbitmq_erlang_cookie: "{{ rabbitmq_erlang_cookie_secret }}" + +# Docker image settings +rabbitmq_image: "rabbitmq:3-management" + +# RabbitMQ Auth Backend URL +rabbitmq_auth_backend_url_base: "http://trackeroo-backend:8080" + +# Kubernetes PersistentVolumeClaim settings +rabbitmq_pvc_storage_class: "longhorn" +rabbitmq_pvc_storage_size: "5Gi" + +# HA settings +rabbitmq_ha_policy_name: "ha-all" +rabbitmq_ha_policy_pattern: ".*" +rabbitmq_ha_policy_definition: '{"ha-mode":"all", "ha-sync-mode":"automatic"}' diff --git a/services/playbooks/roles/rabbitmq/tasks/main.yml b/services/playbooks/roles/rabbitmq/tasks/main.yml index e69de29b..077d8364 100644 --- a/services/playbooks/roles/rabbitmq/tasks/main.yml +++ b/services/playbooks/roles/rabbitmq/tasks/main.yml @@ -0,0 +1,83 @@ +--- +- name: "Create RabbitMQ Kubernetes namespace" + kubernetes.core.k8s: + kubeconfig: /etc/rancher/k3s/k3s.yaml + name: "{{ rabbitmq_namespace }}" + api_version: v1 + kind: Namespace + state: present + +- name: "Create RabbitMQ ConfigMap" + kubernetes.core.k8s: + kubeconfig: /etc/rancher/k3s/k3s.yaml + state: present + namespace: "{{ rabbitmq_namespace }}" + definition: "{{ lookup('template', '01-configmap.yml.j2') | from_yaml }}" + +- name: "Create RabbitMQ Secrets (Erlang Cookie)" + kubernetes.core.k8s: + kubeconfig: /etc/rancher/k3s/k3s.yaml + state: present + namespace: "{{ rabbitmq_namespace }}" + definition: + apiVersion: v1 + kind: Secret + metadata: + name: "{{ rabbitmq_app_name }}-secrets" + stringData: + rabbitmq_erlang_cookie: "{{ rabbitmq_erlang_cookie }}" + +- name: "Create RabbitMQ TLS certificates Secret" + kubernetes.core.k8s: + kubeconfig: /etc/rancher/k3s/k3s.yaml + state: present + namespace: "{{ rabbitmq_namespace }}" + definition: + apiVersion: v1 + kind: Secret + metadata: + name: "rabbitmq-certs" + # Using type Opaque with stringData is straightforward + type: Opaque + stringData: + # These key names must match the paths in rabbitmq.conf + ca_certificate.pem: "{{ ca_certificate }}" + server_certificate.pem: "{{ server_certificate }}" + server_key.pem: "{{ server_key }}" + +- name: "Create RabbitMQ Headless Service for clustering" + kubernetes.core.k8s: + kubeconfig: /etc/rancher/k3s/k3s.yaml + state: present + namespace: "{{ rabbitmq_namespace }}" + definition: "{{ lookup('template', '03-headless-service.yml.j2') | from_yaml }}" + +- name: "Create RabbitMQ StatefulSet" + kubernetes.core.k8s: + kubeconfig: /etc/rancher/k3s/k3s.yaml + state: present + namespace: "{{ rabbitmq_namespace }}" + definition: "{{ lookup('template', '02-statefulset.yml.j2') | from_yaml }}" + +- name: "Create RabbitMQ client-facing Service" + kubernetes.core.k8s: + kubeconfig: /etc/rancher/k3s/k3s.yaml + state: present + namespace: "{{ rabbitmq_namespace }}" + definition: "{{ lookup('template', '04-service.yml.j2') | from_yaml }}" + +- name: "Ensure previous HA Policy Job is removed" + kubernetes.core.k8s: + kubeconfig: /etc/rancher/k3s/k3s.yaml + state: absent + namespace: "{{ rabbitmq_namespace }}" + kind: Job + # The name must match the metadata.name in your J2 template + name: "{{ rabbitmq_app_name }}-set-ha-policy" + +- name: "Create Job to apply RabbitMQ HA Policy" + kubernetes.core.k8s: + kubeconfig: /etc/rancher/k3s/k3s.yaml + state: present + namespace: "{{ rabbitmq_namespace }}" + definition: "{{ lookup('template', '05-policy-job.yml.j2') | from_yaml }}" diff --git a/services/playbooks/roles/rabbitmq/templates/01-configmap.yml.j2 b/services/playbooks/roles/rabbitmq/templates/01-configmap.yml.j2 new file mode 100644 index 00000000..4e10d4cd --- /dev/null +++ b/services/playbooks/roles/rabbitmq/templates/01-configmap.yml.j2 @@ -0,0 +1,49 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: "{{ rabbitmq_app_name }}-config" + namespace: "{{ rabbitmq_namespace }}" +data: + enabled_plugins: | + [rabbitmq_peer_discovery_k8s,rabbitmq_auth_backend_http,rabbitmq_event_exchange,rabbitmq_management,rabbitmq_mqtt]. + + rabbitmq.conf: | + # --- Clustering Configuration --- + cluster_formation.peer_discovery_backend = k8s + cluster_formation.k8s.host = kubernetes.default.svc.cluster.local + cluster_formation.k8s.service_name = {{ rabbitmq_app_name }}-headless + cluster_formation.node_cleanup.interval = 60 + cluster_formation.node_cleanup.only_log_warning = true + cluster_partition_handling = autoheal + queue_master_locator = min-masters + + # TCP Listeners + listeners.tcp.default = 5672 + management.tcp.port = 15672 + + # SSL Configuration + ssl_options.cacertfile = /etc/rabbitmq/certs/ca_certificate.pem + ssl_options.certfile = /etc/rabbitmq/certs/server_certificate.pem + ssl_options.keyfile = /etc/rabbitmq/certs/server_key.pem + ssl_options.verify = verify_none + ssl_options.fail_if_no_peer_cert = false + + # MQTT Configuration + mqtt.listeners.tcp.default = 1883 + mqtt.listeners.ssl.default = 8883 + mqtt.allow_anonymous = false + mqtt.vhost = / + mqtt.exchange = amq.topic + + # Enable custom auth backend + auth_backends.1 = http + auth_http.http_method = post + auth_http.user_path = {{ rabbitmq_auth_backend_url_base }}/auth/user + auth_http.vhost_path = {{ rabbitmq_auth_backend_url_base }}/auth/vhost + auth_http.resource_path = {{ rabbitmq_auth_backend_url_base }}/auth/resource + auth_http.topic_path = {{ rabbitmq_auth_backend_url_base }}/auth/topic + + # Logging + log.console = true + log.console.level = info diff --git a/services/playbooks/roles/rabbitmq/templates/02-statefulset.yml.j2 b/services/playbooks/roles/rabbitmq/templates/02-statefulset.yml.j2 new file mode 100644 index 00000000..d0d1a685 --- /dev/null +++ b/services/playbooks/roles/rabbitmq/templates/02-statefulset.yml.j2 @@ -0,0 +1,61 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: "{{ rabbitmq_app_name }}" + namespace: "{{ rabbitmq_namespace }}" +spec: + serviceName: "{{ rabbitmq_app_name }}-headless" + replicas: {{ rabbitmq_replicas }} + selector: + matchLabels: + app: "{{ rabbitmq_app_name }}" + template: + metadata: + labels: + app: "{{ rabbitmq_app_name }}" + spec: + nodeSelector: + workload-type: apps + containers: + - name: "{{ rabbitmq_app_name }}" + image: "{{ rabbitmq_image }}" + ports: + - name: amqp + containerPort: 5672 + - name: management + containerPort: 15672 + - name: mqtt + containerPort: 1883 + - name: mqtts + containerPort: 8883 + env: + - name: RABBITMQ_ERLANG_COOKIE + valueFrom: + secretKeyRef: + name: "{{ rabbitmq_app_name }}-secrets" + key: rabbitmq_erlang_cookie + volumeMounts: + - name: config-volume + mountPath: /etc/rabbitmq/ + - name: certs-volume + mountPath: /etc/rabbitmq/certs + readOnly: true + - name: data-volume + mountPath: /var/lib/rabbitmq + volumes: + - name: config-volume + configMap: + name: "{{ rabbitmq_app_name }}-config" + - name: certs-volume + secret: + secretName: rabbitmq-certs + volumeClaimTemplates: + - metadata: + name: data-volume + spec: + accessModes: [ "ReadWriteOnce" ] + storageClassName: "{{ rabbitmq_pvc_storage_class }}" + resources: + requests: + storage: "{{ rabbitmq_pvc_storage_size }}" diff --git a/services/playbooks/roles/rabbitmq/templates/03-headless-service.yml.j2 b/services/playbooks/roles/rabbitmq/templates/03-headless-service.yml.j2 new file mode 100644 index 00000000..93b9947e --- /dev/null +++ b/services/playbooks/roles/rabbitmq/templates/03-headless-service.yml.j2 @@ -0,0 +1,17 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: "{{ rabbitmq_app_name }}-headless" + namespace: "{{ rabbitmq_namespace }}" +spec: + clusterIP: None + selector: + app: "{{ rabbitmq_app_name }}" + ports: + - name: amqp + protocol: TCP + port: 5672 + - name: management + protocol: TCP + port: 15672 diff --git a/services/playbooks/roles/rabbitmq/templates/04-service.yml.j2 b/services/playbooks/roles/rabbitmq/templates/04-service.yml.j2 new file mode 100644 index 00000000..f19f4abe --- /dev/null +++ b/services/playbooks/roles/rabbitmq/templates/04-service.yml.j2 @@ -0,0 +1,28 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: "{{ rabbitmq_app_name }}" + namespace: "{{ rabbitmq_namespace }}" +spec: + type: LoadBalancer + loadBalancerIP: "{{ rabbitmq_service_ip }}" + selector: + app: "{{ rabbitmq_app_name }}" + ports: + - name: amqp + protocol: TCP + port: 5672 + targetPort: 5672 + - name: management + protocol: TCP + port: 15672 + targetPort: 15672 + - name: mqtt + protocol: TCP + port: 1883 + targetPort: 1883 + - name: mqtts + protocol: TCP + port: 8883 + targetPort: 8883 diff --git a/services/playbooks/roles/rabbitmq/templates/05-policy-job.yml.j2 b/services/playbooks/roles/rabbitmq/templates/05-policy-job.yml.j2 new file mode 100644 index 00000000..bcaca787 --- /dev/null +++ b/services/playbooks/roles/rabbitmq/templates/05-policy-job.yml.j2 @@ -0,0 +1,28 @@ +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: "{{ rabbitmq_app_name }}-policy-setter" + namespace: "{{ rabbitmq_namespace }}" +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: rabbitmq-policy-setter + image: "{{ rabbitmq_image }}" + command: + - /bin/sh + - -c + - | + echo "Waiting for RabbitMQ service to be ready..." + while ! rabbitmqctl -q -n rabbit@rabbitmq-0.rabbitmq-headless.{{ rabbitmq_namespace }}.svc.cluster.local ping; do + sleep 5 + done + echo "RabbitMQ is up. Applying HA policy..." + rabbitmqadmin \ + -H rabbitmq-service.{{ rabbitmq_namespace }}.svc.cluster.local \ + declare policy \ + name={{ rabbitmq_ha_policy_name }} \ + pattern="{{ rabbitmq_ha_policy_pattern }}" \ + definition='{{ rabbitmq_ha_policy_definition }}' diff --git a/services/playbooks/roles/redis/defaults/main.yml b/services/playbooks/roles/redis/defaults/main.yml new file mode 100644 index 00000000..c2880a20 --- /dev/null +++ b/services/playbooks/roles/redis/defaults/main.yml @@ -0,0 +1,8 @@ +--- +# defaults file for ansible-role-redis +redis_namespace: "data" +redis_app_name: "redis" +redis_image: "redis:latest" + +redis_pvc_storage_class: "longhorn" +redis_pvc_storage_size: "2Gi" diff --git a/services/playbooks/roles/redis/tasks/main.yml b/services/playbooks/roles/redis/tasks/main.yml new file mode 100644 index 00000000..b526ea91 --- /dev/null +++ b/services/playbooks/roles/redis/tasks/main.yml @@ -0,0 +1,10 @@ +--- +- name: "Create Redis StatefulSet" + kubernetes.core.k8s: + kubeconfig: /etc/rancher/k3s/k3s.yaml + state: present + namespace: "{{ redis_namespace }}" + definition: "{{ lookup('template', item) | from_yaml }}" + loop: + - "01-statefulset.yml.j2" + - "02-service.yml.j2" diff --git a/services/playbooks/roles/redis/templates/01-statefulset.yml.j2 b/services/playbooks/roles/redis/templates/01-statefulset.yml.j2 new file mode 100644 index 00000000..ca9867e6 --- /dev/null +++ b/services/playbooks/roles/redis/templates/01-statefulset.yml.j2 @@ -0,0 +1,46 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: "{{ redis_app_name }}" + namespace: "{{ redis_namespace }}" +spec: + serviceName: "{{ redis_app_name }}" + replicas: 1 + selector: + matchLabels: + app: "{{ redis_app_name }}" + template: + metadata: + labels: + app: "{{ redis_app_name }}" + spec: + nodeSelector: + workload-type: apps + securityContext: + fsGroup: 999 + runAsUser: 999 + runAsGroup: 999 + containers: + - name: "{{ redis_app_name }}" + image: "{{ redis_image }}" + ports: + - containerPort: 6379 + name: redis + readinessProbe: + exec: + command: ["redis-cli", "ping"] + initialDelaySeconds: 15 + periodSeconds: 10 + volumeMounts: + - name: data-volume + mountPath: /data + volumeClaimTemplates: + - metadata: + name: data-volume + spec: + accessModes: ["ReadWriteOnce"] + storageClassName: "{{ redis_pvc_storage_class }}" + resources: + requests: + storage: "{{ redis_pvc_storage_size }}" diff --git a/services/playbooks/roles/redis/templates/02-service.yml.j2 b/services/playbooks/roles/redis/templates/02-service.yml.j2 new file mode 100644 index 00000000..9a71fe3e --- /dev/null +++ b/services/playbooks/roles/redis/templates/02-service.yml.j2 @@ -0,0 +1,14 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: "{{ redis_app_name }}" + namespace: "{{ redis_namespace }}" +spec: + type: ClusterIP + selector: + app: "{{ redis_app_name }}" + ports: + - protocol: TCP + port: 6379 + targetPort: 6379 diff --git a/services/playbooks/roles/trackeroo/defaults/main.yml b/services/playbooks/roles/trackeroo/defaults/main.yml new file mode 100644 index 00000000..9967edaf --- /dev/null +++ b/services/playbooks/roles/trackeroo/defaults/main.yml @@ -0,0 +1,14 @@ +--- +# defaults file for ansible-role-trackeroo-backend +trackeroo_namespace: "trackeroo" +trackeroo_app_name: "trackeroo-backend" +trackeroo_image: "ghcr.io/skiby7/trackeroo-backend:latest" +trackeroo_replicas: 2 +trackeroo_users_key: "{{ trackeroo_users_key_secret }}" +trackeroo_service_ip: "10.20.30.55" + +# Dependencies +trackeroo_mongo_service_name: "mongo.data.svc.cluster.local" +trackeroo_redis_service_name: "redis.data.svc.cluster.local" +trackeroo_rabbitmq_service_name: "rabbitmq.trackeroo.svc.cluster.local" +trackeroo_rabbitmq_service_port: 5672 diff --git a/services/playbooks/roles/trackeroo/tasks/main.yml b/services/playbooks/roles/trackeroo/tasks/main.yml new file mode 100644 index 00000000..93c96e38 --- /dev/null +++ b/services/playbooks/roles/trackeroo/tasks/main.yml @@ -0,0 +1,35 @@ +--- +- name: "Create Trackeroo Kubernetes namespace" + kubernetes.core.k8s: + kubeconfig: /etc/rancher/k3s/k3s.yaml + name: "{{ trackeroo_namespace }}" + api_version: v1 + kind: Namespace + state: present + +- name: "Create Trackeroo Backend Secret" + kubernetes.core.k8s: + kubeconfig: /etc/rancher/k3s/k3s.yaml + state: present + namespace: "{{ trackeroo_namespace }}" + definition: + apiVersion: v1 + kind: Secret + metadata: + name: "{{ trackeroo_app_name }}-secret" + stringData: + users_key: "{{ trackeroo_users_key }}" + mqtt_username: "{{ trackeroo_mqtt_username }}" + mqtt_password: "{{ trackeroo_mqtt_password }}" + rabbitmq_username: "{{ trackeroo_rabbitmq_username }}" + rabbitmq_password: "{{ trackeroo_rabbitmq_password }}" + +- name: "Create Trackeroo Backend Deployment and Service" + kubernetes.core.k8s: + kubeconfig: /etc/rancher/k3s/k3s.yaml + state: present + namespace: "{{ trackeroo_namespace }}" + definition: "{{ lookup('template', item) | from_yaml }}" + loop: + - "01-deployment.yml.j2" + - "02-service.yml.j2" diff --git a/services/playbooks/roles/trackeroo/templates/01-deployment.yml.j2 b/services/playbooks/roles/trackeroo/templates/01-deployment.yml.j2 new file mode 100644 index 00000000..f40cc672 --- /dev/null +++ b/services/playbooks/roles/trackeroo/templates/01-deployment.yml.j2 @@ -0,0 +1,93 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: "{{ trackeroo_app_name }}" + namespace: "{{ trackeroo_namespace }}" +spec: + replicas: {{ trackeroo_replicas }} + selector: + matchLabels: + app: "{{ trackeroo_app_name }}" + template: + metadata: + labels: + app: "{{ trackeroo_app_name }}" + spec: + nodeSelector: + workload-type: apps + initContainers: + - name: wait-for-dependencies + image: busybox:1.35 + command: ['sh', '-c', 'until nc -vz {{ trackeroo_mongo_service_name }} 27017 && nc -vz {{ trackeroo_redis_service_name }} 6379; do echo "Waiting for dependencies..."; sleep 2; done;'] + containers: + - name: "{{ trackeroo_app_name }}" + image: "{{ trackeroo_image }}" + imagePullPolicy: Always + ports: + - containerPort: 8080 + name: http + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: MONGO_URI + value: "mongodb://{{ trackeroo_mongo_service_name }}:27017/trackeroo" + - name: REDIS_URI + value: "{{ trackeroo_redis_service_name }}:6379" + - name: RABBITMQ_HOST + value: "{{ trackeroo_rabbitmq_service_name }}" + - name: RABBITMQ_PORT + value: "{{ trackeroo_rabbitmq_service_port }}" + - name: LOG_LEVEL + value: "info" + # Point MQTT to the RabbitMQ service name + - name: MQTT_HOST + value: "{{ rabbitmq_service_ip }}" + - name: MQTT_BROKER + value: "{{ trackeroo_rabbitmq_service_name }}" + - name: MQTT_PORT + value: "1883" + - name: MQTTS_PORT + value: "8883" + # Get the secret from the K8s Secret + - name: USERS_KEY + valueFrom: + secretKeyRef: + name: "{{ trackeroo_app_name }}-secret" + key: users_key + - name: MQTT_USERNAME + valueFrom: + secretKeyRef: + name: "{{ trackeroo_app_name }}-secret" + key: mqtt_username + - name: MQTT_PASSWORD + valueFrom: + secretKeyRef: + name: "{{ trackeroo_app_name }}-secret" + key: mqtt_password + - name: RABBITMQ_USERNAME + valueFrom: + secretKeyRef: + name: "{{ trackeroo_app_name }}-secret" + key: rabbitmq_username + - name: RABBITMQ_PASSWORD + valueFrom: + secretKeyRef: + name: "{{ trackeroo_app_name }}-secret" + key: rabbitmq_password + readinessProbe: + tcpSocket: + port: 8080 + initialDelaySeconds: 10 + periodSeconds: 5 + volumeMounts: + # Mount the same certs used by RabbitMQ + - name: certs-volume + mountPath: /certs + readOnly: true + volumes: + - name: certs-volume + secret: + secretName: rabbitmq-certs diff --git a/services/playbooks/roles/trackeroo/templates/02-service.yml.j2 b/services/playbooks/roles/trackeroo/templates/02-service.yml.j2 new file mode 100644 index 00000000..f8fd9694 --- /dev/null +++ b/services/playbooks/roles/trackeroo/templates/02-service.yml.j2 @@ -0,0 +1,14 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: "{{ trackeroo_app_name }}" + namespace: "{{ trackeroo_namespace }}" +spec: + type: LoadBalancer + loadBalancerIP: {{ trackeroo_service_ip }} + selector: + app: "{{ trackeroo_app_name }}" + ports: + - port: 8080 + targetPort: 8080 diff --git a/services/playbooks/roles/tsdb/defaults/main.yml b/services/playbooks/roles/tsdb/defaults/main.yml index d2f48840..2bbde7e7 100644 --- a/services/playbooks/roles/tsdb/defaults/main.yml +++ b/services/playbooks/roles/tsdb/defaults/main.yml @@ -5,15 +5,15 @@ tsdb_namespace: "data" cluster_name: tsdb instances: 3 image: ghcr.io/skiby7/tsdb:17 -storage_size: 5Gi +storage_size: 20Gi storage_class: longhorn database_name: tracker_db database_user: postgres admin_user: admin apps_user: apps # This should be a secret but rn the cluster is not exposed -admin_password: "administrator" -apps_password: "apps" +admin_password: administrator +apps_password: apps synchronous_method: any synchronous_number: 1 data_durability: required diff --git a/services/playbooks/roles/tsdb/files/01-init.sql b/services/playbooks/roles/tsdb/files/01-init.sql new file mode 100644 index 00000000..63d81558 --- /dev/null +++ b/services/playbooks/roles/tsdb/files/01-init.sql @@ -0,0 +1,34 @@ +CREATE EXTENSION IF NOT EXISTS timescaledb; +CREATE EXTENSION IF NOT EXISTS postgis; +CREATE EXTENSION IF NOT EXISTS postgis_topology; +CREATE EXTENSION IF NOT EXISTS fuzzystrmatch; +CREATE EXTENSION IF NOT EXISTS postgis_tiger_geocoder; +DO +$$ +BEGIN + IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'admin') THEN + CREATE ROLE admin + WITH LOGIN SUPERUSER CREATEDB CREATEROLE REPLICATION BYPASSRLS + PASSWORD 'administrator'; + END IF; +END +$$; + +-- Application role +DO +$$ +BEGIN + IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'apps') THEN + CREATE ROLE apps + WITH LOGIN PASSWORD 'apps'; + END IF; +END +$$; +GRANT ALL PRIVILEGES ON DATABASE tracker_db TO admin; + +-- TimescaleDB specific permissions +GRANT USAGE ON SCHEMA _timescaledb_catalog TO apps; +GRANT USAGE ON SCHEMA _timescaledb_config TO apps; +GRANT USAGE ON SCHEMA _timescaledb_internal TO apps; +GRANT SELECT ON ALL TABLES IN SCHEMA _timescaledb_catalog TO apps; +GRANT SELECT ON ALL TABLES IN SCHEMA _timescaledb_config TO apps; diff --git a/services/playbooks/roles/tsdb/files/02-schema.sql b/services/playbooks/roles/tsdb/files/02-schema.sql new file mode 100644 index 00000000..82869cd1 --- /dev/null +++ b/services/playbooks/roles/tsdb/files/02-schema.sql @@ -0,0 +1,54 @@ +CREATE SCHEMA IF NOT EXISTS trackeroo; + +CREATE TABLE IF NOT EXISTS trackeroo.data ( + ts_unix BIGINT NOT NULL, + ts TIMESTAMPTZ NOT NULL, + dev_id TEXT NOT NULL, + insertion_time TIMESTAMPTZ DEFAULT NOW(), + tag TEXT NOT NULL, + payload JSONB NOT NULL, + PRIMARY KEY (ts, dev_id) +); + +CREATE TABLE IF NOT EXISTS trackeroo.aggregated ( + ts_unix BIGINT NOT NULL, + ts TIMESTAMPTZ NOT NULL, + dev_id TEXT NOT NULL, + route_hash TEXT NOT NULL, + insertion_time TIMESTAMPTZ DEFAULT NOW(), + tag TEXT NOT NULL, + payload JSONB NOT NULL, + + PRIMARY KEY (ts, route_hash, dev_id) +); + +SELECT create_hypertable('trackeroo.data', 'ts', 'dev_id', 16); +SELECT create_hypertable('trackeroo.aggregated', 'ts', 'dev_id', 16); + +CREATE INDEX IF NOT EXISTS idx_trackeroo_data_dev_id ON trackeroo.data(dev_id); +CREATE INDEX IF NOT EXISTS idx_trackeroo_data_tag ON trackeroo.data(tag); +CREATE INDEX IF NOT EXISTS idx_trackeroo_data_ts ON trackeroo.data(ts); + +CREATE INDEX IF NOT EXISTS idx_trackeroo_aggregated_dev_id ON trackeroo.aggregated(dev_id); +CREATE INDEX IF NOT EXISTS idx_trackeroo_aggregated_route_hash ON trackeroo.aggregated(route_hash); +CREATE INDEX IF NOT EXISTS idx_trackeroo_aggregated_tag ON trackeroo.aggregated(tag); +CREATE INDEX IF NOT EXISTS idx_trackeroo_aggregated_ts ON trackeroo.aggregated(ts); + +SELECT add_retention_policy('trackeroo.data', INTERVAL '3 days'); +SELECT add_retention_policy('trackeroo.aggregated', INTERVAL '3 days'); + +-- Now I have to allow apps to read/write on all current tables +GRANT USAGE ON SCHEMA trackeroo TO apps; +-- +-- Grant read/write on all current tables +GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA trackeroo TO apps; + +-- Grant read/write on all sequences (needed if you ever add SERIAL/IDENTITY columns) +GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA trackeroo TO apps; + +-- Ensure future tables/sequences also inherit these permissions +ALTER DEFAULT PRIVILEGES IN SCHEMA trackeroo + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO apps; + +ALTER DEFAULT PRIVILEGES IN SCHEMA trackeroo + GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO apps; diff --git a/services/playbooks/roles/tsdb/tasks/main.yml b/services/playbooks/roles/tsdb/tasks/main.yml index 8db9d16b..29e4c271 100644 --- a/services/playbooks/roles/tsdb/tasks/main.yml +++ b/services/playbooks/roles/tsdb/tasks/main.yml @@ -90,6 +90,22 @@ delegate_to: "{{ groups['servers'][0] }}" run_once: true +- name: Create init scripts ConfigMap + kubernetes.core.k8s: + kubeconfig: /etc/rancher/k3s/k3s.yaml + state: present + definition: + apiVersion: v1 + kind: ConfigMap + metadata: + name: "{{ cluster_name }}-init-scripts" + namespace: "{{ tsdb_namespace }}" + data: + 01-init.sql: "{{ lookup('file', '01-init.sql') }}" + 02-schema.sql: "{{ lookup('file', '02-schema.sql') }}" + delegate_to: "{{ groups['servers'][0] }}" + run_once: true + - name: Apply PostgreSQL cluster manifest kubernetes.core.k8s: kubeconfig: /etc/rancher/k3s/k3s.yaml @@ -116,39 +132,3 @@ postgres_cluster.resources[0].status.phase == "Creating a new replica" delegate_to: "{{ groups['servers'][0] }}" run_once: true - -- name: Grant database access to managed users - kubernetes.core.k8s_exec: - kubeconfig: /etc/rancher/k3s/k3s.yaml - namespace: "{{ tsdb_namespace }}" - pod: "{{ cluster_name }}-1" - command: > - psql -U postgres -d {{ database_name }} -c " - -- Admin user gets full privileges - GRANT ALL PRIVILEGES ON DATABASE {{ database_name }} TO {{ admin_user }}; - - -- Apps user gets full read/write access - GRANT ALL PRIVILEGES ON DATABASE {{ database_name }} TO {{ apps_user }}; - GRANT ALL ON SCHEMA public TO {{ apps_user }}; - GRANT ALL ON ALL TABLES IN SCHEMA public TO {{ apps_user }}; - GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO {{ apps_user }}; - GRANT ALL ON ALL FUNCTIONS IN SCHEMA public TO {{ apps_user }}; - - -- Ensure apps user gets permissions on future objects - ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO {{ apps_user }}; - ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO {{ apps_user }}; - ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON FUNCTIONS TO {{ apps_user }}; - - -- TimescaleDB specific permissions - GRANT USAGE ON SCHEMA _timescaledb_catalog TO {{ apps_user }}; - GRANT USAGE ON SCHEMA _timescaledb_config TO {{ apps_user }}; - GRANT USAGE ON SCHEMA _timescaledb_internal TO {{ apps_user }}; - GRANT SELECT ON ALL TABLES IN SCHEMA _timescaledb_catalog TO {{ apps_user }}; - GRANT SELECT ON ALL TABLES IN SCHEMA _timescaledb_config TO {{ apps_user }}; - " - delegate_to: "{{ groups['servers'][0] }}" - retries: 5 - delay: 10 - register: grant_result - until: grant_result is not failed - run_once: true diff --git a/services/playbooks/roles/tsdb/templates/tsdb-cluster.yml.j2 b/services/playbooks/roles/tsdb/templates/tsdb-cluster.yml.j2 index 57bde231..a96c9f95 100644 --- a/services/playbooks/roles/tsdb/templates/tsdb-cluster.yml.j2 +++ b/services/playbooks/roles/tsdb/templates/tsdb-cluster.yml.j2 @@ -4,6 +4,9 @@ metadata: name: {{ cluster_name }} namespace: {{ tsdb_namespace }} spec: + affinity: + nodeSelector: + workload-type: apps postgresUID: 999 postgresGID: 999 instances: {{ instances }} @@ -19,17 +22,18 @@ spec: size: {{ storage_size }} storageClass: {{ storage_class }} resizeInUseVolumes: true - monitoring: - enabled: {{ monitoring_enabled | default(true) }} bootstrap: initdb: - database: {{ database_name }} - owner: {{ database_user }} - postInitSQL: - - "CREATE EXTENSION IF NOT EXISTS timescaledb;" - - "CREATE EXTENSION IF NOT EXISTS postgis;" - - "CREATE EXTENSION IF NOT EXISTS postgis_topology;" - - "CREATE EXTENSION IF NOT EXISTS pg_stat_statements;" + database: {{ databse_name | default('tracker_db') }} + owner: {{ admin_user | default('admin') }} + # https://cloudnative-pg.io/documentation/preview/bootstrap/#executing-queries-after-initialization + # The ApplicationSQL indicates that the SQL scripts will be executed against the newly created database defined above. + postInitApplicationSQLRefs: + configMapRefs: + - name: "{{ cluster_name }}-init-scripts" + key: 01-init.sql + - name: "{{ cluster_name }}-init-scripts" + key: 02-schema.sql managed: roles: # Admin user with full privileges diff --git a/services/playbooks/secrets.yml b/services/playbooks/secrets.yml new file mode 100644 index 00000000..0f48f4ba --- /dev/null +++ b/services/playbooks/secrets.yml @@ -0,0 +1,302 @@ +$ANSIBLE_VAULT;1.1;AES256 +33323239366637313934376464393335376563373063653666333739633366373335353664306535 +6565316230316662623861326433383933623831393831310a353139316562396539386464336130 +66333038653062616664613330366138616361383465313537316231633763373432353534363565 +3633363336343638330a303166356136653337626366353464346531393165383164353464363231 +34323039366162383861303337333337366636336565373765396662343935336238303063366436 +30383231383737373566376365343931336634613534633031386234396435656365363434383439 +38626665343836323562633339663664393161623861343236313834613136386165336364623034 +31323534643330323235653336633439623764383365303664653435393434373234313866623266 +64633435343234616135623734373435383932393935356235613163633265313339313238336534 +30663232636338303230626462636330306631633837333263636530303762303932373230313762 +63303936313432653432653032363836316264396634306536376635353965656532616138313538 +64666134313064636434386335333162666266383562366561393135393032623436373434373231 +34646266373066616436303761306638653236303938323664333765393462333161633537613736 +34626231343663313131363339643939313861323063643538616364373363323465346638633838 +30336134643362313536346138386464653232343534386335313238656630626134653639623064 +33666262393361393065313330656363633665383039303033303331326663356464316336666266 +33633562323061633436373836613338323363373361373562633065313734663937323538376434 +66636238646262626339666430323434313932343335333363306531633636326337393364333735 +30616436313835323434666431646636333230376165616332383366303664336130653966393162 +33323339313838333761613966323034653665373466396137633032313031616230373065353035 +36393665373430646365636535343163336539393736366661646661643739386636363464616137 +63316434626639383232636466646330653462643333666131643764383230306332393533383865 +38623964623831386639333630333062333061653035396466326461633432616364306335316633 +66326138333638306131663566313737316164663831316238656330646165313465616233326537 +35373462333038373662326638363634323634643034636665356661356264303466306237643336 +39623665613333383765626265653735313163343437616430613036306436613563363339623065 +33623861353462323230373064623630626261356262666466393063633564393335646264303038 +64383837313237376664346631656463393636393937303165373138386165616438663634306634 +64393035326331376135316565313133393337326230333661643533656231386461326632336234 +32636230396437366165333939313365373266326433663030323362373866383433323035316364 +65376366356564303530623936663963633337633063643733643534653164346531326163333433 +33636633303336616233663065643334303431303730383938663136363161333534363036353562 +37383663383335326636313636323266313961343632613263616339656333653931316563393935 +65656461363363663365613834326531633764333465663764306530333739303733303361333033 +62366233646562313464646262613936653137663535333736633035396163366132343637323833 +37363132386436656131303633313239386236346239666261643732663366316233353836613966 +65383138616437373330333766306266363564336338636538326361643734636561303435366561 +31653830656631346636346332353430353264613134636535383330626237353434396433356462 +31653365613831656135343462646538343235396264623837376334616632383730636639636166 +36303436363634373430373763333166326561643163366262666435363338343362316161656630 +64633233346565666161343539336231396334336465643263383866326639646435313731336336 +37666138333564663161396431356134663365616263306662663763336161663436376565323563 +39363261356162663763393864613062373833303832613631393735393865336162366430623766 +64376434356664356662316134373766643631646163376430643532393835663533616332663235 +36333661656434316633376561633763653163663131353339353164373361326463666330343836 +61363663346265336361663864303235303636333662366164353663336363356633333432656438 +30613138313962356136323839323639303266636133633366633531326234643364653730626564 +61613064363864326436636464393138376366336366656237356232316439316330383163323166 +34346138636233336363363834623761323635373938323166313962396233313562353232343330 +66343261343833643963396134346130613062633436633332633936323336653331616637393562 +32636566333564336538363865303239393032633139633766356535326336623333336234376165 +38663835636466313234663934366164396363626261336166373165313739313064396662336564 +36323631623734663837646437636264353232393366666532386665393563396336346666623739 +30623965663532633766636132623632343230336235633761323937373861363262653332643832 +30313966333031356165646333316331613137346334626335663831383863646563346464623063 +62643836353332383130666138383364653332623661633061343461646639393439646435613362 +30336532663563656334613337326361613437383331393739323966633635663937663233343431 +32323435306133363835333031343763303832353432313364646439626265353430666537613066 +31393134653638386166336139393034313863356231333131656437656233666339626334623136 +66623762663433303864616430393962643732393631373230616562653833373563306265356237 +39343734336130663734636431373539393731363835626434633961393439663534613931313463 +62633436303336316131313531643634613465343931356634653163303538333837643932336339 +38313563343639356538626639623033633735636135326434313839616164643562333262383431 +30633135306233623534333131376563346135623066353962333663623137643565333364396164 +63356331353332316165306431356462313833636462373661333436363638666133316166656537 +39323663626564303634333264346530373339616136333836653037393930316666343739663533 +31363637343437383030323833376361373465373539363061326164336638613364663966313439 +62663862663635333737393365346438363832353436323132636536353330353430303333343533 +62333039383366366633653263663365343933633034326132363730313134353836373936343532 +30343966353964643134613264313064613130666464393837323363653336393135363034643231 +66366665663964623665353039643230303362323666616534363561653837623935386331636461 +62306365656139386630303662323938323662633134386266383537376134343035376534613435 +61356536313933346537333261343334633161336262313634613463326437633538656632396630 +38346434646233663131373162353462313336366636653734373466323565363430306463313431 +64633761343131383762356535353037333666633337393835363463333161313534643335366664 +35373966653137393262383366663863366531333332623166386666653263646632616461633138 +62333039633734396339643362643931386434613662303263333838623039636663646464326134 +36303261353637653033633035653464363364366438353561306664646338363463356266663562 +64363932363065613130323561366432643734356538366538303566633862303161363332666261 +64373065663330653463646431353834643762373430626636366536613634303830656531663763 +30313565636662303735343635646135393866626535336439643366306263336334333066313335 +61333265343635623066363531363637613665613035396135393435363831313433383638643337 +33323530646139353339356335396162383530613062363833373731343036356234666332343534 +63633330613331323363643862656438613564613330363864616262313936656461356239636136 +38323865383563616665383737343832313630383432643839313666333538376531626236313863 +32323566343764386534323664393561323761626139393633646133393964373861666139336434 +31326135396663313132633135353633613161643962643765363839313932376232343864613334 +33316230636266373635366665646433303138333436396333363939643335643230366232376563 +36623466393564663562376366643864363661613964663539623366386261653339653933326165 +38663334363361323231393138383466333863336438643561656432303739623437323562333863 +30373666316334626433643931303535333161343064656264646536663766386332643263643731 +37333131306233356538613035336338356662383532343562666439346438393534633361303939 +37653239353233663430373163666663373164663138356164396534313835333534636131636266 +62306235363332626136623832316365626562383465396235376161383564666338633632396532 +66326536663931656164383635613035613731373830333166636330636637643461306338383139 +31326435356534373866376131343031373533653735326637636334343566336137663165313030 +30323833376562326639343234666362393466623464396563393838306561373939313237356166 +38386633393137336135356662366436633265653530666138373935323665383334306633346533 +32376566616164646363623131346662393265653661353139343437373838666230323364346464 +36373036373631326632376631383362356366376634623063396461306162393165663937323830 +39623738313764343938323664353262616261333362323934343864366232336563383265303238 +61336437326136656531383030633764343331653463363832663833663765646462313564303038 +65316530616263636231656334623039633839333230303538383637626362643636306162643464 +30666633336237386231613037313064306163316139663038383935346235306536373262373064 +66303636383733626331373435613632666438636366396266613532373066613164653261396565 +35623737356266643731623262333332666331383536356634363230636430323761613962306138 +66373765356231353333666133653263616338373431636336366462613762343533333065303835 +61326637613731386333356563643737656135663438343639653231323636363764316439353763 +35663632636535303338636532353835623661396338366534333630616538393231633862323831 +35343832303866353361306233643762653831373239643565386132653539633837396131373562 +31383135626131326133323466356465323431376264613462376133383464666237336438623138 +63313862643865356331633532646638326637333035646361343263333538356331356338666435 +33656638653763396535323832333765646531616366323361373436383261646536376338386233 +66663039653566666133613535383132376537333962303665633136613161303062376138316133 +30613134383332626538626265656461386633396430646132613430666165306637323433653634 +36333639356334636562353739383566386365356664663034326637613362356532643066326535 +38643136303066623130326137373331626438353366616264326339656636616232336130663463 +30666461313038333337613635656166353530323866353030363736336365333062376139366632 +66656333363664326637376132663933343133343462356137363639303363633534643131383561 +32663834393363366232336432353761353538373734643730336433333239363965656262303737 +63346137636438316139623732306236316465613863653532663535363139613566323838303336 +61336138343365393439313536616234313733346531313237373563613862613431373731643465 +38643663303762353364313238613336383139646335623365616463393033363834373865353931 +65353433306338333561333961646662356431376131626638303265636136363665333863383639 +32363730613761626633613136636139323130303434643439336134633136646630333266643432 +33346430633435313365306331393133646164336166393233663339633465616532663035383261 +65663065643637643963373934613865643832316434363131363431396564356462663866663437 +31653166386561363932346531633666326136363732326233356665383636623334306431326532 +37653534383533346432663636373730646164623763363565663832366530636163306365303161 +37393534373831623931633938326639333861346233333833363938346565616436333734323835 +61356566396439636130663037383733313234643665666538643863613165366431333964373963 +65383336366535373663356137366234623466333937303230343637396639666238393136303431 +34313038663939656232313862623263663734613334363430663063376136336333316164653931 +65636639343232656332623835663234336362333131316531356534383135363238366431653237 +66666261353137623362613966613762343633396636623163336635323039303132613165353337 +36306238636635353431363634336562613239613162323764383838663361366132383937666535 +36323637376539393030613437386166396161396532633865366663613263396366303735646233 +32646338393136323361363332323030656162346338663436613365666362323733633862383231 +36383733616138613932386236353264303532386566326662646335383838376430363164646164 +31306338356133363434613334373339343839336161313330323431386330666330386361353832 +61313636653837656630353035306336646434616265383635333130383939613939303965316366 +62656632316536613964343864636430306132636137386663386431346139656635323238313433 +32623761363433333265333538643533353862633534306435663132626431386461303964333337 +62633132636431326331653336373936346533393964663737643733656635353161633463336432 +31313331613334346562633264353035643661366362316235653539306433623039663932633533 +63383639653864336134323331343037373362363632663566363063373763356636373033623661 +37353530613462303936343435336130306465656330353430336664376166323464303734316538 +39666262363232353832396663376139386335653734653739333539376237656365393033656634 +37363462356465646432326666313438316638646437363439303862666137356465303237666332 +34313263356165333065643533306139386634333432333930623230633433373261343032326265 +39323138356239383530373934633034656430383631323238366635353939626135373737376530 +62643835663539303433613931613838636537336437396131393465633631663637316532346462 +37613862313534333066333563343333613931613433373362326335373734316638653933363765 +34626638373232656231396165613364333061373830303132383439303330643764396564656131 +63653766356663333138656161313831373537646166326232636338363564366438643764343339 +66386335313237616266343033663332383432386132336566663430393636653239636563373934 +38636564643261646235666133383263323838326635366164353531613238666363363633346463 +65343231656231646431643666376639666638653836396234393034656666346163616464663136 +31313234383363383437396333623338666163326336366663643265656631396138636662303562 +36623434613666613663386366383335383237626433663164643564323064346436666131363936 +33303637663035353632393633303635333764633837313432323863333937336435653630636331 +30303664386666346665623265343434366364326636623762363936316330316132666231303230 +30643461653938396230653566336132613832656237643535306335366435383930306430306233 +63656465666333363430336664376133316135353431623739393261366139376636663866626266 +38666665303538616437613232626134303764643163653132393036373033343239316665616331 +62313261373137346531386434626535373665393432343763663562353232383534613266346166 +38386231666239326464323636393338303563626135353864663236313938316663613136656132 +33366331646563333862616438613137396662643838303034356430326631646430303633653033 +35613833336639363532353865303063356465383735633365636465653138656561626434363565 +31663139623663663634643266316633346237623961666534303338323839303432316333313333 +34313533373162373164396635363230356661376237643838353733336630323833353066646234 +64353938323437306264653063346465653562326262393330343661373533323033363861646538 +30323365616533633330356532666631656433313066613632316162386561333234666366613632 +30326431363932643238353263303532303635653365353633623532313938326436373762646138 +66313032303937383065343336633130646136363933386437343734396664663265323030376431 +66666630326262396332386565323235316335386462323961376362613162336333313838656533 +32643137616531613433643764376232616166353130326463636233333262636136336436393134 +64656465303139643738376266366165666661636631386362646338383836363563373633313362 +32653539613139653532376462303538663134313639366663643531653639393633396462633230 +61353533396337396534623139346638376237653235626564643231366136663330363061396231 +36613532373833313136336630613038626163336330633930313434333137343932646133633635 +33663834643866333562643263653532333931383162353531383432313332643363646438633338 +33663336363031333166346436613939346466343236326336613837313737656265383136613262 +39616430383232643635313061333861343034363261373431653038636432373233333539343530 +66633333623563336134396335393638396539306466313161663562636531396365636266666433 +35323435623536316463326538376136333334353163626164353232343364616139663764646266 +36323139363232326434323764616333653962373562613336383161633439316164393462393834 +31346330396638303938636536333036306636363233656539613438623331343939313863613566 +33346262333361376535303031356337383235643262616137393236363764623462373464393363 +64626334356236323336633965326437326465393265613233663033346666346561373231633062 +64636563656539393730626239313261313136353734656432336332386532363338626137616562 +36353937343239666266363162393766663435393631356166396566626463343563363964346561 +65633962616362643734373237633934383261623063396662343664313737616239353034346462 +34346334633830636330666366663638663335376639323539646339323166386535636361346236 +37393638316132643131396237616433346236373036393530316133326639623638313966666335 +38653336396233653834363562373663316535396539356365633430633966613938373738303330 +63666637393164373063653663363665393232313532346162356666623839396534383638393564 +32393162646430346364313131656237353934646135346363386265626433633834333537366466 +34646136323964366665353033656663383632636633376432383765623730643933623839623335 +62633036643232643830626138303737343464303464313066383431616265633466393364346561 +34653534626365333533653434643733383861393464353635313738393735303163666536613930 +32376266613563363931373262343363633433303534353966336632616261643263396239316238 +32616336373034313339336133656462636637326238643733343831393962383066373764373732 +36376665306236663732656533613363336162373363616262373962643866323132653938386139 +38626461376261633637613334333337643565376636323737313232323365393864303137323730 +35333566393835386564363438653531343730623964306435663232383164346238663038366365 +30636263343533353138656339383633633338663630636461656130616335623836346337333535 +36343964653664393833353833373864613364353432333933623139323639353432343630313764 +39663835343338333130643365613134356361663631633138623263363735303764636265643665 +31313663646536663533303033633832373934323131333832303336376237623137646531333231 +34326632303134653033663931633664376537303366613630643963326136623461636166663464 +35393334666631643431656339313334363831366431623937303131626238633732383539373631 +63623033396461333632623463633738386639323930643366663530623961623465633466353331 +30373231333632356464623737393234623666363466313032653639396662646436623933646530 +34383938656534646261666333643864616138393533393266616161356633356332643431353637 +62366335663332353066303761643061306435663437656162316665366233336639356530653931 +66393566613765303937613939646361343163663066313631366133653739333436333033306337 +31313862636239306664393538333361303833646630383331623765306137313236373132326466 +31363162393232656538303966343038313631323765636332316339346436626136386336376366 +30353063303433353665613861386265333765336463643064333339626566666363633864616265 +39393836303530663064663562356537643765346233623038333432333162393539303934376530 +39636161643431653163663662373032363265373861323730373530666334626433346235363439 +36633663336239623566393232363761363237666263616531666438396461326536613036363366 +38613265653137306237396134633238633138336463383132333162393830303866323365626330 +62316633316262613638623935346232313530656132326133653133613662336137323463366630 +35313766653962653062646134313937646533326436366364376132326661613131353033323662 +37386365323834323366373263393636383566303261653138323833313331643035623234656462 +64363236373831636434313631663739396363393433336230626635396633346532643461326461 +35306439666131383762616463653765373438613636653934656630623133333961653565306465 +33666139336464336535363835643664303862393161386466373235346237636134386463323232 +33643830643133303732613738666635393864323936653739376233393831326136623764306335 +63386562656332353333616239333831383335616439396266326139646361643262313736626237 +61366439616264353266313462636539323764626362346337343435646462306564646463346438 +37313133373430616635356334623136373639656331306561376131383036346231623730663465 +38396666356665633039636334613632653231393765643639626437393337613237636361393563 +63346234383033353861383134366462643461393535616465316434346664306134366635623162 +64306436636631306136653134326237323236656364386362346632383730663032343035363532 +63353531303061353532386639646164313465633866383839626438333866323337336165353766 +33376530656133333261633737383731333031396365356530343433663863383736643733376464 +31653164663537393934656432393830623563623163326334333664363838326566616265373266 +31626237343537383930613563363832383139336130393434376632643762376433336633356337 +37333665333435623030306435656339636234616331633961313739316238323563383065623734 +64373635383137626565346432383633376337653134646232616435663439343439666363646431 +63623536383065396165613839613631363635666535386336396433623261303234393666653064 +36326637373563323932636231656466306663313934613238333162636639643337313662313139 +35623735646532306331373664396133666262326333613635313739653363376464616139643061 +39643434343163666130393633666637356232626131623332626661323036616338633237393239 +62353033333637323336663264373138313931323531623632333136353564623239653533356235 +62663366333036316165373831636637636166663062383738333164396465323336323938303733 +38373732303736663365633065636564323732623732346665643430616532653438383363653436 +36393637323263313233616536646662613861306135376130396534363538643361336662343262 +38616536666436663635373661653362343562633164313662373666616161393266316436646664 +31373762343062636461636163633266353564313362646539303365303630333035323136393062 +31373738613536383661313133643065373138623166373032313765336138353233613236366630 +37366630666430313464623234303135356536393438636138653731663938666532663834373665 +37666464386136323838623263633335636338353433643834323533316636303833306539373965 +30316462643264656433323431643263373934363062326662306164633238333933663934383362 +34343730353836323834376438333431366536376539613235373634316261356665366235643163 +34383239643936383238646430313237376363386639373035376165646164316332373233363235 +30356630393330303764613736333235646266396537653330396131303434643138616665616166 +63316138633534633961336437353539613664613931663234623839653963343539646462373731 +34353238623232346161376232333434663033303433353164356637303531663832663563646536 +34333336316665656233303438383836313637623866336639323539323962316434666235616264 +34306165653938626461326233343338613465666339326336333838346438653433363930616438 +64316437333337626431326562643734623834393263396139653362313937353737383033376638 +66336664303238323566356565333565343736366137633236666230313965383062326661376232 +34323264316663333332316433313835383765376438623436393161623465363863346537336636 +38613638343934336562303431386135643934656336383633386665616534323630346331393565 +64643336346335656330366566656231353862626364386638303137633535663339396262623339 +64643964313561323632613631393534653833343638646630333832303436336536363833386362 +38333836646336346536333632653762316463383032633566376436313866396361396262623331 +64646137383635636336643136373436636166376136643236633431343530663439323434663164 +66323566613664653630616630626334326438313132653535616165383931653262386539626361 +37346364326538643737396439643435316130666164363835666137653861633139383235316334 +61653632343363613832316461323838366466303837353762333034663335306561366338626131 +63376533303236316163303063316362643939353037376434353366323461396561393666613461 +30303264666365373265393033653536313630326539633563393239663264356232386437363131 +32376130616162393261363136666130303235393664343466343235343065313432386631383265 +63343563383234353332636436633066306431333465336136663036356465396461393064316663 +36353632343064343361343261643832376431333837343038653837346437383339356662333234 +32343763633862623434336566633332643365353939373039326139643239353133376133313630 +35666531626364353537346633356536303433356639306536633264346562646665346363633531 +32396130613762623336396563303762643937353032313836366331623237323234646333353333 +62623365366236366164343038336661343466373664616366663037323339373635656664663236 +33343037376365323638653561356261376635376438616437386365626236353232646335383530 +62353936666263346630623037636164353438643030316432386139356136313133666638306438 +63613864316135373733343832346531656362636265663462366133636563356566386662343036 +33653462326633613861346431623033343432623139386165363961376164616534303536623631 +35303536643336613265386637613866373732383132613931613039656139633365373434613363 +65373132653661376333373138303766343838373833623131366330663734393762656138333832 +32376436373433306562316333663035343262363538303834333138306436653430633035633866 +64353531383461633962336132326566356662386361346134346532333238643432653034326465 +62396134326332636534666433626336653663313963313034356332656265653731326566393463 +32353062303438643563363466373836643835616263393964323631333564396138336637636637 +62333933333538656130646362386366656233633039613132633466363239613933343639333465 +63313338306662313866346263363034666534616236613866663935316238356235643938393364 +32663664366333356463313232376631303035653638616533633063336131363933616261633836 +35313966336434373430393832313136393835383236643430646533363533613734343464323530 +31666466653531333465383864643138643265663033333230306566303964643463316463373532 +33353138396164306334373435306636623138653532313437613461303839623131623130633065 +66646365306635306665376238353663383839643434643566653237373032656361 diff --git a/services/playbooks/site.yml b/services/playbooks/site.yml index 0bf9e5f6..4591949c 100644 --- a/services/playbooks/site.yml +++ b/services/playbooks/site.yml @@ -1,11 +1,88 @@ - name: Deploy tsdb hosts: servers - tags: tsdb + tags: + - tsdb + - data roles: - tsdb - name: Deploy pgAdmin hosts: servers - tags: pgadmin + tags: + - pgadmin + - management roles: - pgadmin + +- name: Deploy Mongo Express + hosts: servers + tags: + - mongo-express + - management + roles: + - mongo-express + +- name: Deploy rabbitmq + hosts: servers + tags: + - rabbitmq + - trackeroo + vars_files: + - secrets.yml + roles: + - rabbitmq + +- name: Deploy mongo + hosts: servers + tags: + - mongo + - data + roles: + - mongo + +- name: Deploy redis + hosts: servers + tags: + - redis + - data + roles: + - redis + +- name: Deploy trackeroo + hosts: servers + tags: + - trackeroo + vars_files: + - secrets.yml + roles: + - trackeroo + +- name: Deploy Flink + hosts: servers + tags: + - flink + - data + roles: + - flink + +- name: Deploy Brokeroo + hosts: servers + tags: + - brokeroo + - trackeroo + roles: + - brokeroo + +- name: Deploy Grafana + hosts: servers + tags: grafana + roles: + - grafana + +- name: Deploy Kafka and Zookeeper + hosts: servers + tags: + - kafka + - data + roles: + - kafka diff --git a/services/reset.sh b/services/reset.sh new file mode 100755 index 00000000..fdecf97f --- /dev/null +++ b/services/reset.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +kubectl delete namespace data trackeroo diff --git a/services/run.sh b/services/run.sh index 8245cf2c..6cd32fdf 100755 --- a/services/run.sh +++ b/services/run.sh @@ -3,4 +3,4 @@ RUN_TAG="" if [[ -n "$1" ]]; then RUN_TAG="-t $1" fi -ansible-playbook -i inventory/hosts.yml playbooks/site.yml $RUN_TAG +ansible-playbook -i inventory/hosts.yml playbooks/site.yml $RUN_TAG --ask-vault-pass diff --git a/trackeroo-backend/docker-compose.yml b/trackeroo-backend/docker-compose.yml deleted file mode 100644 index 23363184..00000000 --- a/trackeroo-backend/docker-compose.yml +++ /dev/null @@ -1,134 +0,0 @@ -services: - trackeroo-backend: - build: - context: src - dockerfile: Dockerfile - healthcheck: - test: nc -z localhost 8080 - interval: 5s - timeout: 5s - retries: 5 - start_period: 10s - secrets: - - users_key - ports: - - "8080:8080" - depends_on: - mongo: - condition: service_healthy - environment: - MONGO_URI: mongodb://mongo:27017/trackeroo - LOG_LEVEL: debug - MQTT_HOST: localhost - MQTT_PORT: 1883 - MQTTS_PORT: 8883 - - mongo: - image: mongo:latest - command: ["mongod", "--bind_ip_all"] - healthcheck: - test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] - interval: 5s - timeout: 5s - retries: 5 - start_period: 5s - volumes: - - ./mongo/init.js:/docker-entrypoint-initdb.d/init.js:ro - - ./mongo/data:/data/db - - rabbitmq: - image: rabbitmq:3-management - container_name: rabbitmq - ports: - - "5672:5672" # AMQP - - "15672:15672" # Management UI - - "1883:1883" # MQTT - - "8883:8883" # MQTT - environment: - RABBITMQ_DEFAULT_USER: admin - RABBITMQ_DEFAULT_PASS: admin123 - RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS: '-rabbitmq_mqtt tcp_listeners [{"0.0.0.0",1883}]' - volumes: - - ./rabbitmq/etc:/etc/rabbitmq/ - - ./rabbitmq/data:/var/lib/rabbitmq - command: > - bash -c "rabbitmq-plugins enable --offline rabbitmq_mqtt; - rabbitmq-server" - - tsdb: - image: ghcr.io/skiby7/tsdb:latest - healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres -d tracker_db"] - interval: 10s - timeout: 5s - retries: 5 - container_name: tsdb - command: ["postgres", "-c", "shared_preload_libraries=timescaledb"] - environment: - POSTGRES_DB: tracker_db - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - - # Custom Postgres parameters - POSTGRES_INITDB_ARGS: "--auth-host=scram-sha-256 --auth-local=scram-sha-256" - - volumes: - - ./tsdb/data:/var/lib/postgresql/data - - ./tsdb/initdb:/docker-entrypoint-initdb.d - - pgadmin: - image: dpage/pgadmin4:latest - container_name: pgadmin - restart: always - environment: - PGADMIN_DEFAULT_EMAIL: admin@admin.com - PGADMIN_DEFAULT_PASSWORD: admin - ports: - - "8085:80" - volumes: - - ./tsdb/pgadmin_data:/var/lib/pgadmin - depends_on: - tsdb: - condition: service_healthy - - test-db-ingestion: - build: - context: brokeroo - dockerfile: Dockerfile - environment: - MQTT_BROKER: mqtt://rabbitmq:1883 - MQTT_CLIENT_ID: apps - MQTT_USERNAME: "apps" - MQTT_PASSWORD: "apps" - POSTGRES_URL: postgres://admin:administrator@tsdb:5432/tracker_db?sslmode=disable - TOPIC_PATTERN: j/data/+/+ - depends_on: - tsdb: - condition: service_healthy - rabbitmq: - condition: service_started - grafana: - image: grafana/grafana:latest - container_name: grafana - restart: unless-stopped - depends_on: - tsdb: - condition: service_healthy - environment: - # Default admin user - GF_SECURITY_ADMIN_USER: admin - GF_SECURITY_ADMIN_PASSWORD: admin123 - - # Optional: Configure Grafana to use Postgres as its own database - GF_DATABASE_TYPE: postgres - GF_DATABASE_HOST: tsdb:5432 - GF_DATABASE_NAME: tracker_db - GF_DATABASE_USER: admin - GF_DATABASE_PASSWORD: administrator - ports: - - "3000:3000" - volumes: - - ./grafana:/var/lib/grafana -secrets: - users_key: - file: secrets/users_key diff --git a/trackeroo-backend/mongo/init.js b/trackeroo-backend/mongo/init.js deleted file mode 100644 index d34663fb..00000000 --- a/trackeroo-backend/mongo/init.js +++ /dev/null @@ -1,244 +0,0 @@ -db = db.getSiblingDB("trackeroo-backend"); - -db.devices.insertMany([ - { - _id: "trk-185ed4ee37ca7c3effa0a5b2", - name: "friendly_curie", - status: { connected: false, last_message: "2025-07-31T13:06:04Z" }, - device_type: "private_transport", - private_key: "bFXAEffsy373z91WyDKMjjTC4ohtEdDNL0GRLnB56vE=", - created_at: ISODate("2025-08-15T19:44:04Z"), - }, - { - _id: "trk-185ed4ee37ccac29287f5bfc", - name: "condescending_volta", - status: { connected: false, last_message: "2025-08-12T20:42:04Z" }, - device_type: "food", - private_key: "jimaUfli1tsLN74h0cw/BFaFv6hP1j9L2yL4Kj8xdbE=", - created_at: ISODate("2025-07-29T02:22:04Z"), - }, - { - _id: "trk-185ed4ee37ccdc2145feb671", - name: "angry_maxwell", - status: { connected: false, last_message: "2025-08-15T22:53:04Z" }, - device_type: "food", - private_key: "/MwFVNFhL4CjpxUrMCNdTCIEB5f1m+WYDB5HiovjGQg=", - created_at: ISODate("2025-08-15T21:44:04Z"), - }, - { - _id: "trk-185ed4ee37ccfde9d6c0d6e8", - name: "naughty_dijkstra", - status: { connected: false, last_message: "2025-08-09T15:57:04Z" }, - device_type: "other", - private_key: "Os55R80WTUqX3QzjUcZ28fOkZ8hSTTkHyyCJouRTsvo=", - created_at: ISODate("2025-08-10T09:11:04Z"), - }, - { - _id: "trk-185ed4ee37cd2182bfe59e58", - name: "compassionate_oppenheimer", - status: { connected: false, last_message: "2025-08-17T19:41:04Z" }, - device_type: "public_transport", - private_key: "J01HGzrPva4ifT6bbluiKxbmCvCrkVeEeE/ErwO3QG4=", - created_at: ISODate("2025-07-28T22:05:04Z"), - }, - { - _id: "trk-185ed4ee37cd490baea45bda", - name: "xenodochial_carmack", - status: { connected: false, last_message: "2025-08-19T20:11:04Z" }, - device_type: "public_transport", - private_key: "xs4eexjMfi8yAf3twb5JK1kf/M+ecNe04eBHk40QYX0=", - created_at: ISODate("2025-08-13T13:45:04Z"), - }, - { - _id: "trk-185ed4ee37cd6ed388af77cf", - name: "competent_rutherford", - status: { connected: true, last_message: "2025-08-24T16:54:04Z" }, - device_type: "other", - private_key: "ho7cIlOSzsA4JK8xgvQ5mZItQHejeNVJMiPlZlQDdsU=", - created_at: ISODate("2025-08-16T05:20:04Z"), - }, - { - _id: "trk-185ed4ee37cd9c9ae6661e2a", - name: "curious_feynman", - status: { connected: true, last_message: "2025-08-14T17:49:04Z" }, - device_type: "public_transport", - private_key: "k4m5V2iigmvFiZL1tCv86a67iDONDTIHc+EUgx1MJAE=", - created_at: ISODate("2025-08-17T02:44:04Z"), - }, - { - _id: "trk-185ed4ee37cdc28785bce7c3", - name: "optimistic_babbage", - status: { connected: true, last_message: "2025-08-01T15:07:04Z" }, - device_type: "private_transport", - private_key: "688Q6WjzDH/4WYp8cuTDphX79qcg5dfBbIVN6mrmSNw=", - created_at: ISODate("2025-08-14T17:53:04Z"), - }, - { - _id: "trk-185ed4ee37cdeb517d0f7754", - name: "clever_newton", - status: { connected: true, last_message: "2025-08-21T03:09:04Z" }, - device_type: "food", - private_key: "/v4md60k/G8HYuhO99H//ebJLwbJopj1UoqYdT6H78M=", - created_at: ISODate("2025-08-02T12:12:04Z"), - }, - { - _id: "trk-185ed4ee37ce0c720dd6b6a7", - name: "amazing_galileo", - status: { connected: false, last_message: "2025-08-04T17:33:04Z" }, - device_type: "food", - private_key: "hWbnNYge0XmpBJRS3ND6xi/sCSvO/dkRoX9FpEo6ynU=", - created_at: ISODate("2025-08-21T02:31:04Z"), - }, - { - _id: "trk-185ed4ee37ce2fdf6aa5b0c2", - name: "confident_faraday", - status: { connected: true, last_message: "2025-08-06T09:42:04Z" }, - device_type: "valuable", - private_key: "TaNKv1efsNjS2SbY8GrOFFjxJdsWdVEg4+sf0+Nzhak=", - created_at: ISODate("2025-07-26T18:46:04Z"), - }, - { - _id: "trk-185ed4ee37ce5573f69f6d9a", - name: "boring_wozniak", - status: { connected: true, last_message: "2025-08-03T11:48:04Z" }, - device_type: "public_transport", - private_key: "hiT4ylaWbrgNi/j5cfpqRrIbpYhylJlrrreNP5/z6H4=", - created_at: ISODate("2025-08-04T07:28:04Z"), - }, - { - _id: "trk-185ed4ee37ce7a0dfb8b1f3e", - name: "loving_lovelace", - status: { connected: false, last_message: "2025-08-11T22:36:04Z" }, - device_type: "public_transport", - private_key: "OmU8hdxx2NXHbYNGg2pnkW1o0tDZOLuFuGh9l0XthDI=", - created_at: ISODate("2025-08-16T14:36:04Z"), - }, - { - _id: "trk-185ed4ee37ceb441c570927d", - name: "eager_gauss", - status: { connected: true, last_message: "2025-08-05T10:23:04Z" }, - device_type: "public_transport", - private_key: "VCso/AY0FDj3Dn3W7qsyG8O1Cw1N9jNWAUhe5LyIVOU=", - created_at: ISODate("2025-08-18T11:27:04Z"), - }, - { - _id: "trk-185ed4ee37cee05aaace21f4", - name: "relaxed_turing", - status: { connected: false, last_message: "2025-08-22T01:50:04Z" }, - device_type: "public_transport", - private_key: "ePjiwq3Ne8Ul1Lq7lKr4Te7qLjZrZoZmNm27QvsdnC0=", - created_at: ISODate("2025-08-20T19:07:04Z"), - }, - { - _id: "trk-185ed4ee37cf0317a6b1369e", - name: "ecstatic_riemann", - status: { connected: false, last_message: "2025-07-30T13:51:04Z" }, - device_type: "other", - private_key: "lYdhyWbtFqaj8gwEUTxSRBKKqjpXXZSB3U4tK+Wtv6M=", - created_at: ISODate("2025-08-06T19:39:04Z"), - }, - { - _id: "trk-185ed4ee37cf279576ae7859", - name: "cranky_ampere", - status: { connected: false, last_message: "2025-08-01T06:52:04Z" }, - device_type: "valuable", - private_key: "d67x1z0chKBpBkNN7Ce85VmfjD8jNrdONqIKpuYxff4=", - created_at: ISODate("2025-08-13T06:15:04Z"), - }, - { - _id: "trk-185ed4ee37cf4ae833197cd7", - name: "zealous_berners", - status: { connected: true, last_message: "2025-08-14T03:40:04Z" }, - device_type: "private_transport", - private_key: "25Sffi26mxx5WQzKNG8BOJ3mWzz4t/ozGZ6Kiw6RwTI=", - created_at: ISODate("2025-08-14T12:32:04Z"), - }, - { - _id: "trk-185ed4ee37cf703dc5aaff65", - name: "gracious_hawking", - status: { connected: false, last_message: "2025-08-14T00:55:04Z" }, - device_type: "private_transport", - private_key: "HJU90iCVuc9v/NG/4LwajgPazaMEB/MMudUr7tcUt2U=", - created_at: ISODate("2025-07-27T11:14:04Z"), - }, - { - _id: "trk-185ed4ee37cf9e30f1e863d3", - name: "elastic_fourier", - status: { connected: false, last_message: "2025-08-06T14:39:04Z" }, - device_type: "food", - private_key: "SVt4fI8q3dlprxG5DeFxq6SqupNDjUJe4kPvV75010s=", - created_at: ISODate("2025-08-14T17:11:04Z"), - }, - { - _id: "trk-185ed4ee37cfc76e9abfc570", - name: "adoring_morse", - status: { connected: false, last_message: "2025-08-02T18:17:04Z" }, - device_type: "food", - private_key: "MNdO+vjoETIErKlOjp8/4tuDAsQgxCCHK0J3EA42YvA=", - created_at: ISODate("2025-08-20T22:04:04Z"), - }, - { - _id: "trk-185ed4ee37cff0467daf9583", - name: "charming_dirac", - status: { connected: true, last_message: "2025-08-19T04:10:04Z" }, - device_type: "public_transport", - private_key: "seVb1nczvvmMoa9ZVD5G/spvaWgSc0jrlXTlhfHaevs=", - created_at: ISODate("2025-08-03T20:24:04Z"), - }, - { - _id: "trk-185ed4ee37d0145f6f8c7cbc", - name: "crazy_coulomb", - status: { connected: true, last_message: "2025-08-14T23:26:04Z" }, - device_type: "other", - private_key: "WlGQ1R8RGYW/y+xjv2zqfjtc2SRCB3mBcKKIapuOLn0=", - created_at: ISODate("2025-08-23T04:34:04Z"), - }, - { - _id: "trk-185ed4ee37d03b0718bd5337", - name: "amazing_turing", - status: { connected: true, last_message: "2025-07-26T12:10:04Z" }, - device_type: "food", - private_key: "80If8DZk+DoVlCaAvN1q6ymdV3RH819Oi6UdwnsKIh4=", - created_at: ISODate("2025-08-18T02:12:04Z"), - }, - { - _id: "trk-185ed4ee37d05c72bf5fa09b", - name: "affectionate_bell", - status: { connected: false, last_message: "2025-08-17T01:36:04Z" }, - device_type: "food", - private_key: "ZY5ahbWsLBdvG1kVyxjv5TjnfprhkXZ76fE39nJbIOM=", - created_at: ISODate("2025-08-21T09:29:04Z"), - }, - { - _id: "trk-185ed4ee37d07d379e9bbf9a", - name: "clever_fermi", - status: { connected: true, last_message: "2025-08-09T12:01:04Z" }, - device_type: "public_transport", - private_key: "glZ5czrF8KFlMsuZLKwbfxGTAB7XFN4z6p590v2GLk4=", - created_at: ISODate("2025-07-26T09:13:04Z"), - }, - { - _id: "trk-185ed4ee37d0a1a3e172b57e", - name: "intelligent_jobs", - status: { connected: false, last_message: "2025-08-03T20:39:04Z" }, - device_type: "private_transport", - private_key: "DHVy34kRhC9U+csNCXSB5H9sqMsgSRFnf7F3PkS0ytg=", - created_at: ISODate("2025-08-19T12:46:04Z"), - }, - { - _id: "trk-185ed4ee37d0d4ffb88e9b5d", - name: "jolly_gates", - status: { connected: true, last_message: "2025-07-30T06:48:04Z" }, - device_type: "food", - private_key: "iddUvURXAy6suor45cofmOultpvA6wwg4/gwI828+CQ=", - created_at: ISODate("2025-08-22T14:02:04Z"), - }, - { - _id: "trk-185ed4ee37d10d5563bdc108", - name: "serene_hopper", - status: { connected: true, last_message: "2025-07-29T04:30:04Z" }, - device_type: "public_transport", - private_key: "GYUE096x+XHFrKX+b5xy4IpgkRy+BRy6YxQrdgNLWi4=", - created_at: ISODate("2025-08-07T01:31:04Z"), - }, -]); diff --git a/trackeroo-backend/rabbitmq/etc/enabled_plugins b/trackeroo-backend/rabbitmq/etc/enabled_plugins deleted file mode 100644 index 96ecaf1a..00000000 --- a/trackeroo-backend/rabbitmq/etc/enabled_plugins +++ /dev/null @@ -1 +0,0 @@ -[rabbitmq_auth_backend_http,rabbitmq_management,rabbitmq_mqtt]. diff --git a/trackeroo-backend/run.sh b/trackeroo-backend/run.sh index 071d4324..06db270b 100755 --- a/trackeroo-backend/run.sh +++ b/trackeroo-backend/run.sh @@ -3,5 +3,5 @@ cleanup() { docker compose down } -#trap cleanup EXIT -docker compose up --build -d +trap cleanup EXIT +docker compose up --build diff --git a/trackeroo-backend/src/Dockerfile b/trackeroo-backend/src/Dockerfile index eb66b165..07b2ebc5 100644 --- a/trackeroo-backend/src/Dockerfile +++ b/trackeroo-backend/src/Dockerfile @@ -29,9 +29,10 @@ WORKDIR /app # Copy the compiled binary from the builder stage COPY --from=builder /app/trackeroo-backend . -COPY certs /certs + # Expose the port your app listens on (adjust as needed) -EXPOSE 8081 +EXPOSE 8080 # Command to run the application ENTRYPOINT ["./trackeroo-backend"] + diff --git a/trackeroo-backend/src/cmd/trackeroo-backend/main.go b/trackeroo-backend/src/cmd/trackeroo-backend/main.go index bd184592..b0713671 100644 --- a/trackeroo-backend/src/cmd/trackeroo-backend/main.go +++ b/trackeroo-backend/src/cmd/trackeroo-backend/main.go @@ -17,18 +17,28 @@ import ( ) func main() { - logger.InitLogger() + defer logger.CloseLogger() fmt.Println(service.Art) config.LoadConfig() ctx, cancel := context.WithCancel(context.Background()) defer cancel() - if err := service.InitDb(ctx); err != nil { + if err := service.InitDB(ctx); err != nil { logger.Fatal(err.Error()) } + service.InitConnectivityCache() + defer service.DeinitConnectivityCache() + + service.StartRabbitWatcher(ctx) + service.InitQueue("data") + go service.QueuesCleanUp() + tdmClient := service.NewTdmClient() + tdmClient.Start() + defer tdmClient.Stop() + logger.Debug("Initializing login route") loginRouter := router.NewRouter(). AddHandler("POST /", handler.HandleLogin). @@ -64,15 +74,24 @@ func main() { AddHandler("POST /topic", handler.TopicAuth). Finalize() + logger.Debug("Initializing publish router") + publishRouter := router.NewRouter(). + AddHandler("POST /publish", handler.PublishPayload). + AddMiddleware(middleware.MqttAuth). + Finalize() + logger.Debug("Initializing main route") mainRouter := router.NewRouter(). AddHandler("GET /health", handler.HealthCheck). + AddHandler("OPTIONS /tdm/publish", handler.Options). AddMiddleware(middleware.Logging). AddMiddleware(middleware.Recover). + AddMiddleware(middleware.CORSMiddleware). AddSubroute("/login/", loginRouter). AddSubroute("/devices/", devicesRouter). AddSubroute("/users/", usersRouter). AddSubroute("/auth/", authRouter). + AddSubroute("/tdm/", publishRouter). Finalize() server := http.Server{ @@ -101,7 +120,7 @@ func main() { } // Disconnect MongoDB - if err := service.CloseDb(shutdownCtx); err != nil { + if err := service.CloseDB(shutdownCtx); err != nil { logger.Fatal("MongoDB disconnect failed: %s", err.Error()) } } diff --git a/trackeroo-backend/src/go.mod b/trackeroo-backend/src/go.mod index 93c8521a..62ab1594 100644 --- a/trackeroo-backend/src/go.mod +++ b/trackeroo-backend/src/go.mod @@ -8,14 +8,32 @@ require ( ) require ( + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/eclipse/paho.mqtt.golang v1.5.0 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect github.com/klauspost/compress v1.16.7 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/montanaflynn/stats v0.7.1 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/redis/go-redis/v9 v9.13.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/streadway/amqp v1.1.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect golang.org/x/crypto v0.26.0 // indirect - golang.org/x/sync v0.8.0 // indirect + golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect + golang.org/x/net v0.27.0 // indirect + golang.org/x/sync v0.15.0 // indirect + golang.org/x/sys v0.34.0 // indirect golang.org/x/text v0.17.0 // indirect + modernc.org/libc v1.66.3 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect + modernc.org/sqlite v1.38.2 // indirect ) diff --git a/trackeroo-backend/src/go.sum b/trackeroo-backend/src/go.sum index c42504a5..7172eaab 100644 --- a/trackeroo-backend/src/go.sum +++ b/trackeroo-backend/src/go.sum @@ -1,15 +1,37 @@ +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/eclipse/paho.mqtt.golang v1.5.0 h1:EH+bUVJNgttidWFkLLVKaQPGmkTUfQQqjOsyvMGvD6o= +github.com/eclipse/paho.mqtt.golang v1.5.0/go.mod h1:du/2qNQVqJf/Sqs4MEL77kR8QTqANF7XU7Fk0aOTAgk= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/redis/go-redis/v9 v9.13.0 h1:PpmlVykE0ODh8P43U0HqC+2NXHXwG+GUtQyz+MPKGRg= +github.com/redis/go-redis/v9 v9.13.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/streadway/amqp v1.1.0 h1:py12iX8XSyI7aN/3dUT8DFIDJazNJsVJdxNVEpnQTZM= +github.com/streadway/amqp v1.1.0/go.mod h1:WYSrTEYHOXHd0nwFeUXAe2G2hRnQT+deZJJf88uS9Bg= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= @@ -25,19 +47,28 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -50,3 +81,11 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= +modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek= +modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= diff --git a/trackeroo-backend/src/internal/handler/devices.go b/trackeroo-backend/src/internal/handler/devices.go index 85998f32..71329383 100644 --- a/trackeroo-backend/src/internal/handler/devices.go +++ b/trackeroo-backend/src/internal/handler/devices.go @@ -11,18 +11,6 @@ import ( "trackeroo-backend/internal/service" ) -func GetDevices(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - devices, err := service.GetDevices(ctx) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - json.NewEncoder(w).Encode(model.Error{Error: err.Error()}) - return - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(devices) -} - func CreateDevice(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -61,10 +49,40 @@ func GetDevice(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusNotFound) return } + status, err := service.GetDeviceStatus(ctx, device.ID) + if err == nil { + device.Status = status + } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(device) } +func GetDevices(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + devices, err := service.GetDevices(ctx) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(model.Error{Error: err.Error()}) + return + } + ids := make([]string, len(devices)) + for i, d := range devices { + ids[i] = d.ID + } + s, err := service.GetDevicesStatus(ctx, ids) + if err == nil { + for i := range devices { + status, ok := s[devices[i].ID] + if ok { + devices[i].Status = status + } + } + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(devices) +} + func GetCredentials(w http.ResponseWriter, r *http.Request) { ctx := r.Context() params := r.URL.Query() @@ -110,10 +128,7 @@ func GetDeviceCredentials(w http.ResponseWriter, r *http.Request) { ctx := r.Context() params := r.URL.Query() secureParameter := params.Get("secure") - secure := false - if strings.ToLower(secureParameter) == "true" { - secure = true - } + secure := strings.ToLower(secureParameter) == "true" id := r.PathValue("id") creds, err := service.GetDeviceCredentials(ctx, id) if err != nil { diff --git a/trackeroo-backend/src/internal/handler/login.go b/trackeroo-backend/src/internal/handler/login.go index 9224855b..d9ab9f61 100644 --- a/trackeroo-backend/src/internal/handler/login.go +++ b/trackeroo-backend/src/internal/handler/login.go @@ -18,13 +18,13 @@ func HandleLogin(w http.ResponseWriter, r *http.Request) { } defer r.Body.Close() - logger.Info("Login request by: %+v", data) + logger.Info("Login request by %s", data.Username) if !service.ValidateLogin(r.Context(), data) { w.WriteHeader(http.StatusUnauthorized) - json.NewEncoder(w).Encode(model.Error{Error: "Unauthorized"}) return } + user, _ := service.GetUserByName(r.Context(), data.Username) jwtToken, claims, err := service.GenerateJWT(user) if err != nil { @@ -32,6 +32,7 @@ func HandleLogin(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(model.Error{Error: err.Error()}) return } + user.LastLogin = time.Now() user.SessionToken = jwtToken user.ExpiresAt, _ = claims["exp"].(time.Time) diff --git a/trackeroo-backend/src/internal/handler/mqtt.go b/trackeroo-backend/src/internal/handler/mqtt.go new file mode 100644 index 00000000..8d25a339 --- /dev/null +++ b/trackeroo-backend/src/internal/handler/mqtt.go @@ -0,0 +1,48 @@ +package handler + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "trackeroo-backend/internal/model" + "trackeroo-backend/internal/service" +) + +func PublishPayload(w http.ResponseWriter, r *http.Request) { + username := r.Header.Get("Username") + if username == "" { + w.WriteHeader(http.StatusBadRequest) + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(model.Error{Error: "Please use a valid username"}) + return + } + query := r.URL.Query() + + topic := query.Get("topic") + if topic != "data" { + w.WriteHeader(http.StatusBadRequest) + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(model.Error{Error: "Topic not allowed"}) + return + } + tag := query.Get("tag") + if tag == "" { + w.WriteHeader(http.StatusBadRequest) + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(model.Error{Error: "Tag cannot be empty"}) + return + } + + payload, err := io.ReadAll(r.Body) + defer r.Body.Close() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(model.Error{Error: fmt.Sprintf("Cannot read payload: %v", err)}) + return + } + service.EnqueueData("data", username, tag, string(payload)) + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(model.Details{Details: "ok!"}) +} diff --git a/trackeroo-backend/src/internal/handler/mqttauth.go b/trackeroo-backend/src/internal/handler/mqttauth.go index 84b10f1f..fdf7bdd3 100644 --- a/trackeroo-backend/src/internal/handler/mqttauth.go +++ b/trackeroo-backend/src/internal/handler/mqttauth.go @@ -7,8 +7,12 @@ import ( "trackeroo-backend/internal/logger" "trackeroo-backend/internal/model" "trackeroo-backend/internal/service" + + "go.mongodb.org/mongo-driver/mongo" ) +var isUserCache = service.NewSimpleCache() + func UserAuth(w http.ResponseWriter, r *http.Request) { ctx := r.Context() /* Every response should be 200 */ @@ -26,16 +30,12 @@ func UserAuth(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "deny") return } - logger.Info("Authenticating device: %s ***", form.Username) + logger.Debug("Authenticating device: %s ***", form.Username) _, err := service.GetUserByName(ctx, form.Username) if err == nil { logger.Info("Found admin user %s, validating...", form.Username) - login := model.Login{ - Username: form.Username, - Password: form.Password, - } - valid := service.ValidateLogin(ctx, login) - logger.Info("Valid: %v", valid) + valid := service.ValidateLogin(ctx, form) + logger.Debug("Valid: %v", valid) if !valid { fmt.Fprint(w, "deny") return @@ -59,12 +59,13 @@ func UserAuth(w http.ResponseWriter, r *http.Request) { } return } - logger.Info("Device %s authenticated", form.Username) + logger.Debug("Device %s authenticated", form.Username) fmt.Fprint(w, "allow") } func TopicAuth(w http.ResponseWriter, r *http.Request) { /* Every response should be 200 */ + ctx := r.Context() w.WriteHeader(http.StatusOK) if err := r.ParseForm(); err != nil { http.Error(w, "Cannot parse form", http.StatusBadRequest) @@ -80,18 +81,35 @@ func TopicAuth(w http.ResponseWriter, r *http.Request) { Permission: r.FormValue("permission"), } - if form.Username == "apps" { - logger.Info("App %s authenticated for topic %s", form.Username, form.Topic) + isUserVal := isUserCache.Get(form.Username) + if isUserVal == nil { + _, err := service.GetUserByName(ctx, form.Username) + switch err { + case mongo.ErrNoDocuments: + isUserCache.Set(form.Username, false) + isUserVal = any(false) + case nil: + isUserCache.Set(form.Username, true) + isUserVal = any(true) + default: + logger.Error("Cannot retrieve %s: %v", form.Username, err) + return + } + } + isUser := isUserVal.(bool) + + if isUser { + logger.Debug("App %s authenticated for topic %s", form.Username, form.Topic) fmt.Fprint(w, "allow") return } - logger.Info("Authenticating device %s for topic %s vhost %s resource %s permission %s", form.Username, form.Topic, form.Vhost, form.Resource, form.Permission) + logger.Debug("Authenticating device %s for topic %s vhost %s resource %s permission %s", form.Username, form.Topic, form.Vhost, form.Resource, form.Permission) if strings.Contains(form.Topic, form.Username) { - logger.Info("Device %s authenticated for topic %s", form.Username, form.Topic) + logger.Debug("Device %s authenticated for topic %s", form.Username, form.Topic) fmt.Fprint(w, "allow") return } - logger.Info("Device %s not authenticated for topic %s", form.Username, form.Topic) + logger.Debug("Device %s not authenticated for topic %s", form.Username, form.Topic) fmt.Fprint(w, "deny") } diff --git a/trackeroo-backend/src/internal/handler/options.go b/trackeroo-backend/src/internal/handler/options.go new file mode 100644 index 00000000..72db2d3b --- /dev/null +++ b/trackeroo-backend/src/internal/handler/options.go @@ -0,0 +1,12 @@ +package handler + +import ( + "net/http" +) + +func Options(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, Username") + w.WriteHeader(http.StatusOK) +} diff --git a/trackeroo-backend/src/internal/logger/logger.go b/trackeroo-backend/src/internal/logger/logger.go index 5329bcdf..955890ec 100644 --- a/trackeroo-backend/src/internal/logger/logger.go +++ b/trackeroo-backend/src/internal/logger/logger.go @@ -9,6 +9,7 @@ import ( type Logger struct { logLevel int + logChan chan map[string]any } const ( @@ -25,11 +26,34 @@ var logger *Logger func newLogger(logLevel int) *Logger { return &Logger{ logLevel: logLevel, + logChan: make(chan map[string]any, 1024), } } +func (l *Logger) run() { + for msg := range l.logChan { + level, ok := msg["log_level"].(int) + if !ok { + continue + } + if level < l.logLevel { + continue + } + message, ok := msg["message"].(string) + if !ok { + continue + } + fmt.Printf("%s %s", getPrefixLogString(level), message) + } +} + +func (l Logger) close() { + close(l.logChan) +} + func InitLogger() { logLevel := strings.ToUpper(os.Getenv("LOG_LEVEL")) + fmt.Println("######", logLevel) switch logLevel { case "DEBUG": logger = newLogger(DEBUG) @@ -46,6 +70,11 @@ func InitLogger() { default: logger = newLogger(INFO) } + go logger.run() +} + +func CloseLogger() { + logger.close() } func getPrefixLogString(level int) string { @@ -65,27 +94,43 @@ func getPrefixLogString(level int) string { } func Debug(fmtStr string, args ...any) { - if logger.logLevel <= DEBUG { - fmt.Printf(getPrefixLogString(DEBUG)+fmtStr+"\n", args...) + logger.logChan <- map[string]any{ + "log_level": DEBUG, + "message": fmt.Sprintf(fmtStr+"\n", args...), } + // if logger.logLevel <= DEBUG { + // fmt.Printf(getPrefixLogString(DEBUG)+fmtStr+"\n", args...) + // } } func Info(fmtStr string, args ...any) { - if logger.logLevel <= INFO { - fmt.Printf(getPrefixLogString(INFO)+fmtStr+"\n", args...) + logger.logChan <- map[string]any{ + "log_level": INFO, + "message": fmt.Sprintf(fmtStr+"\n", args...), } + // if logger.logLevel <= INFO { + // fmt.Printf(getPrefixLogString(INFO)+fmtStr+"\n", args...) + // } } func Warning(fmtStr string, args ...any) { - if logger.logLevel <= WARNING { - fmt.Printf(getPrefixLogString(WARNING)+fmtStr+"\n", args...) + logger.logChan <- map[string]any{ + "log_level": WARNING, + "message": fmt.Sprintf(fmtStr+"\n", args...), } + // if logger.logLevel <= WARNING { + // fmt.Printf(getPrefixLogString(WARNING)+fmtStr+"\n", args...) + // } } func Error(fmtStr string, args ...any) { - if logger.logLevel <= ERROR { - fmt.Printf(getPrefixLogString(ERROR)+fmtStr+"\n", args...) + logger.logChan <- map[string]any{ + "log_level": ERROR, + "message": fmt.Sprintf(fmtStr+"\n", args...), } + // if logger.logLevel <= ERROR { + // fmt.Printf(getPrefixLogString(ERROR)+fmtStr+"\n", args...) + // } } func Fatal(fmtStr string, args ...any) { diff --git a/trackeroo-backend/src/internal/middleware/auth.go b/trackeroo-backend/src/internal/middleware/auth.go index 8145539d..7ec7954d 100644 --- a/trackeroo-backend/src/internal/middleware/auth.go +++ b/trackeroo-backend/src/internal/middleware/auth.go @@ -30,3 +30,32 @@ func Auth(next http.Handler) http.Handler { next.ServeHTTP(w, r) }) } + +func MqttAuth(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + logger.Debug("Checking mqtt auth...") + ctx := r.Context() + username := r.Header.Get("Username") + key, err := service.GetDeviceKey(ctx, username) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + logger.Error("Error getting the device key (id %s): %v", username, err) + json.NewEncoder(w).Encode(model.Error{Error: "Unauthorized!"}) + return + } + if res, claims, err := service.ValidateDeviceJWT(key, r.Header.Get("Authorization")); err != nil || !res || claims["sub"] != username { + w.WriteHeader(http.StatusUnauthorized) + json.NewEncoder(w).Encode(model.Error{Error: "Unauthorized!"}) + if err == nil { + logger.Error("Failed to validate device JWT for %s", username) + } else if claims["sub"] != username { + logger.Error("Invalid username in JWT: should be %s, found %s", username, claims["sub"]) + } else { + logger.Error("Failed to validate device JWT for %s: %v", username, err) + } + return + } + logger.Debug("Device %s authenticated", username) + next.ServeHTTP(w, r) + }) +} diff --git a/trackeroo-backend/src/internal/middleware/cors.go b/trackeroo-backend/src/internal/middleware/cors.go new file mode 100644 index 00000000..7629fde4 --- /dev/null +++ b/trackeroo-backend/src/internal/middleware/cors.go @@ -0,0 +1,12 @@ +package middleware + +import "net/http" + +func CORSMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") + next.ServeHTTP(w, r) + }) +} diff --git a/trackeroo-backend/src/internal/middleware/logger.go b/trackeroo-backend/src/internal/middleware/logger.go index deb7b55d..27b3fcd7 100644 --- a/trackeroo-backend/src/internal/middleware/logger.go +++ b/trackeroo-backend/src/internal/middleware/logger.go @@ -26,6 +26,8 @@ func Logging(next http.Handler) http.Handler { next.ServeHTTP(wrapped, r) if wrapped.statusCode < 400 { logger.Info("%v %v %v %v", wrapped.statusCode, r.Method, r.URL.Path, time.Since(start)) + } else if wrapped.statusCode < 500 { + logger.Warning("%v %v %v %v", wrapped.statusCode, r.Method, r.URL.Path, time.Since(start)) } else { logger.Error("%v %v %v %v", wrapped.statusCode, r.Method, r.URL.Path, time.Since(start)) } diff --git a/trackeroo-backend/src/internal/model/devices.go b/trackeroo-backend/src/internal/model/devices.go index a891ecba..1d5e57c4 100644 --- a/trackeroo-backend/src/internal/model/devices.go +++ b/trackeroo-backend/src/internal/model/devices.go @@ -15,15 +15,17 @@ const ( ) type DeviceStatus struct { - Connected bool `bson:"connected" json:"connected"` - LastMessage time.Time `bson:"last_message" json:"last_message"` + Connected bool `bson:"connected" json:"connected"` + LastConnection time.Time `bson:"last_connection" json:"last_connection"` + LastDisconnection time.Time `bson:"last_disconnection" json:"last_disconnection"` + LastIP string `bson:"last_ip" json:"last_ip"` } type Device struct { ID string `bson:"_id,omitempty" json:"id"` Name string `bson:"name" json:"name"` - Status DeviceStatus `bson:"status" json:"status"` DeviceType string `bson:"device_type" json:"device_type"` + Status DeviceStatus `json:"status"` PrivateKey string `bson:"private_key" json:"private_key"` CreatedAt time.Time `bson:"created_at" json:"created_at"` } @@ -45,7 +47,7 @@ type DeviceCredentials struct { } func GenDeviceID() string { - b := make([]byte, 8) // 4 bytes → 8 hex chars + b := make([]byte, 8) // 8 bytes → 16 hex chars _, err := rand.Read(b) if err != nil { panic(err) diff --git a/trackeroo-backend/src/internal/model/mqtt.go b/trackeroo-backend/src/internal/model/mqtt.go index cb721051..cde1e399 100644 --- a/trackeroo-backend/src/internal/model/mqtt.go +++ b/trackeroo-backend/src/internal/model/mqtt.go @@ -1,15 +1,13 @@ package model -type MqttUserAuthRequest struct { - Username string `json:"username"` - Password string `json:"password"` -} - -type MqttTopicAuthRequest struct { - Username string `json:"username"` - Vhost string `json:"vhost"` - Resource string `json:"resource"` - Topic string `json:"topic"` - Name string `json:"name"` - Permission string `json:"permission"` -} +type ( + MqttUserAuthRequest = Login + MqttTopicAuthRequest struct { + Username string `json:"username"` + Vhost string `json:"vhost"` + Resource string `json:"resource"` + Topic string `json:"topic"` + Name string `json:"name"` + Permission string `json:"permission"` + } +) diff --git a/trackeroo-backend/src/internal/model/users.go b/trackeroo-backend/src/internal/model/users.go index f60e3b4f..a0f09ca7 100644 --- a/trackeroo-backend/src/internal/model/users.go +++ b/trackeroo-backend/src/internal/model/users.go @@ -14,49 +14,3 @@ type User struct { IssuedAt time.Time `bson:"issued_at" json:"issued_at"` ExpiresAt time.Time `bson:"expires_at" json:"expires_at"` } - -func DefaultUsers() []User { - user1 := User{ - ID: "", - Username: "leonardo", - Role: "admin", - PasswordHash: "$2b$12$pVXfJaREXTdMyri7Z61KjebMVwCCo0aientxTAis/G57NTc/WW6CO", - LastLogin: time.Now(), - SessionToken: "", - IssuedAt: time.Now(), - ExpiresAt: time.Now().Add(time.Hour * 24), - } - - user2 := User{ - ID: "", - Username: "simone", - Role: "admin", - PasswordHash: "$2b$12$WT0QBYqqOdYY4i2SlIKFueoz5AM6wcIBqf9BV3Sz8AUD1nit.bWJu", - LastLogin: time.Now(), - SessionToken: "", - IssuedAt: time.Now(), - ExpiresAt: time.Now().Add(time.Hour * 24), - } - /* RabbitMQ user */ - user3 := User{ - ID: "", - Username: "admin", - Role: "admin", - PasswordHash: "$2b$12$w.6Q5rCSQfGkDLo2ZUPKt.rnrZnlygqC88tksctnDPCfAfJlN.Av6", - LastLogin: time.Now(), - SessionToken: "", - IssuedAt: time.Now(), - ExpiresAt: time.Now().Add(time.Hour * 24), - } - user4 := User{ - ID: "", - Username: "apps", - Role: "user", - PasswordHash: "$2b$12$8Y2URxHnBABN9G6nj0eLxO65tvty8H8NyjQd1h35dMi8KiEuZS1bG", - LastLogin: time.Now(), - SessionToken: "", - IssuedAt: time.Now(), - ExpiresAt: time.Now().Add(time.Hour * 24), - } - return []User{user1, user2, user3, user4} -} diff --git a/trackeroo-backend/src/internal/service/cache.go b/trackeroo-backend/src/internal/service/cache.go new file mode 100644 index 00000000..975c885b --- /dev/null +++ b/trackeroo-backend/src/internal/service/cache.go @@ -0,0 +1,45 @@ +package service + +import ( + "sync" +) + +type SimpleCache struct { + items map[string]any + m sync.RWMutex +} + +func NewSimpleCache() *SimpleCache { + return &SimpleCache{ + items: make(map[string]any), + m: sync.RWMutex{}, + } +} + +func (kc *SimpleCache) Get(key string) any { + kc.m.RLock() + defer kc.m.RUnlock() + item, ok := kc.items[key] + if !ok { + return nil + } + return item +} + +func (kc *SimpleCache) Set(key string, value any) { + kc.m.Lock() + defer kc.m.Unlock() + kc.items[key] = value +} + +func (kc *SimpleCache) Invalidate(key string) { + kc.m.Lock() + defer kc.m.Unlock() + delete(kc.items, key) +} + +func (kc *SimpleCache) Clear() { + kc.m.Lock() + defer kc.m.Unlock() + kc.items = make(map[string]any) +} diff --git a/trackeroo-backend/src/internal/service/connectivity.go b/trackeroo-backend/src/internal/service/connectivity.go new file mode 100644 index 00000000..cd096b7a --- /dev/null +++ b/trackeroo-backend/src/internal/service/connectivity.go @@ -0,0 +1,229 @@ +// Package service provides access to DB, cache, login validation and redis. +package service + +import ( + "context" + "fmt" + "strconv" + "time" + "trackeroo-backend/internal/logger" + "trackeroo-backend/internal/model" + + "github.com/redis/go-redis/v9" +) + +var ( + redisClient *redis.Client = nil + fallbackCache *SimpleCache = nil + useFallback bool = false +) + +func InitConnectivityCache() { + ctx := context.Background() + if redisClient == nil { + options := redis.Options{ + Addr: GetenvOrDefault("REDIS_URI", "redis:6379"), + } + redisClient = redis.NewClient(&options) + _, err := redisClient.Ping(ctx).Result() + if err != nil { + logger.Warning("Could not connect to Redis: %v", err) + logger.Warning("Falling back to SimpleCache") + useFallback = true + } else { + logger.Info("Connected to Redis!") + } + } + if fallbackCache == nil { + fallbackCache = NewSimpleCache() + } +} + +func DeinitConnectivityCache() { + if !useFallback { + redisClient.Close() + } +} + +func getRedisClient() (*redis.Client, *SimpleCache) { + if useFallback { + return nil, fallbackCache + } + + // Always returning the fallbackCache so that it can be used + // if something goes wrong with redis + return redisClient, fallbackCache +} + +func mapToStruct(m map[string]string) (model.DeviceStatus, error) { + connected, err := strconv.ParseBool(m["connected"]) + if err != nil { + return model.DeviceStatus{}, err + } + + lastConn, err := strconv.ParseInt(m["last_connection"], 10, 64) + if err != nil { + return model.DeviceStatus{}, err + } + + lastDisconn, err := strconv.ParseInt(m["last_disconnection"], 10, 64) + if err != nil { + return model.DeviceStatus{}, err + } + + return model.DeviceStatus{ + Connected: connected, + LastConnection: time.Unix(lastConn, 0), + LastDisconnection: time.Unix(lastDisconn, 0), + LastIP: m["last_ip"], + }, nil +} + +func structToMap(status model.DeviceStatus) map[string]string { + return map[string]string{ + "connected": strconv.FormatBool(status.Connected), + "last_connection": strconv.FormatInt(status.LastConnection.Unix(), 10), + "last_disconnection": strconv.FormatInt(status.LastDisconnection.Unix(), 10), + "last_ip": status.LastIP, + } +} + +func getLastRedisKey(ctx context.Context, rdb *redis.Client, devID, key string) (string, error) { + data, err := rdb.HGet(ctx, devID, key).Result() + if err != nil && err != redis.Nil { + logger.Warning("Cannot get %s %s: %v", devID, key, err) + } else { + if err != nil { + return data, err + } + } + return data, err +} + +func SetDeviceStatus(ctx context.Context, devID, ip string, timestamp time.Time, connected bool) { + rdb, fbc := getRedisClient() + status := model.DeviceStatus{ + Connected: connected, + LastIP: ip, + } + if connected { + status.LastConnection = timestamp + status.LastDisconnection = time.Unix(0, 0) + } else { + status.LastConnection = time.Unix(0, 0) + status.LastDisconnection = timestamp + } + if rdb != nil { + var ( + data string + err error + t time.Time + ) + if connected { + data, err = getLastRedisKey(ctx, rdb, devID, "last_disconnection") + } else { + data, err = getLastRedisKey(ctx, rdb, devID, "last_connection") + } + + if err == nil { + i, err := strconv.ParseInt(data, 10, 64) + if err == nil { + t = time.Unix(i, 0) + if connected { + status.LastDisconnection = t + } else { + status.LastConnection = t + } + } + } + + rdb.HSet(ctx, devID, structToMap(status)) + } else { + data := fbc.Get(devID) + s, ok := data.(model.DeviceStatus) + if ok { + if connected { + status.LastDisconnection = s.LastDisconnection + } else { + status.LastConnection = s.LastConnection + } + } + fbc.Set(devID, status) + } +} + +func GetDeviceStatus(ctx context.Context, devID string) (model.DeviceStatus, error) { + rdb, fbc := getRedisClient() + var ( + data any + status model.DeviceStatus + err error + ok bool + ) + if rdb != nil { + data, err = rdb.HGetAll(ctx, devID).Result() + if err != nil { + return model.DeviceStatus{}, err + } + d, _ := data.(map[string]string) + status, err = mapToStruct(d) + } else { + data := fbc.Get(devID) + status, ok = data.(model.DeviceStatus) + if !ok { + err = fmt.Errorf("cannot find device %s", devID) + status = model.DeviceStatus{} + } + } + return status, err +} + +func GetDevicesStatus(ctx context.Context, devIDs []string) (map[string]model.DeviceStatus, error) { + rdb, fbc := getRedisClient() + result := make(map[string]model.DeviceStatus) + var firstErr error + + if rdb != nil { + pipe := rdb.Pipeline() + cmds := make([]*redis.MapStringStringCmd, len(devIDs)) + for i, id := range devIDs { + cmds[i] = pipe.HGetAll(ctx, id) + } + _, err := pipe.Exec(ctx) + if err != nil { + firstErr = err + } + + for i, cmd := range cmds { + d, err := cmd.Result() + if err != nil && firstErr == nil { + firstErr = err + } + if len(d) == 0 { + result[devIDs[i]] = model.DeviceStatus{} + continue + } + status, err := mapToStruct(d) + if err != nil && firstErr == nil { + firstErr = err + } + result[devIDs[i]] = status + } + } else { + for _, id := range devIDs { + data := fbc.Get(id) + if data != nil { + result[id] = model.DeviceStatus{} + continue + } + status, ok := data.(model.DeviceStatus) + if !ok { + result[id] = model.DeviceStatus{} + continue + } + result[id] = status + } + } + + return result, firstErr +} diff --git a/trackeroo-backend/src/internal/service/db.go b/trackeroo-backend/src/internal/service/db.go index 21216293..e1d9ab23 100644 --- a/trackeroo-backend/src/internal/service/db.go +++ b/trackeroo-backend/src/internal/service/db.go @@ -3,7 +3,6 @@ package service import ( "context" "errors" - "trackeroo-backend/internal/config" "trackeroo-backend/internal/logger" "trackeroo-backend/internal/model" @@ -16,7 +15,13 @@ import ( var MongoClient *mongo.Client -func InitDb(ctx context.Context) error { +var ( + keysCache = NewSimpleCache() + devicesCache = NewSimpleCache() + credentialsCache = NewSimpleCache() +) + +func InitDB(ctx context.Context) error { var err error mongoURI := config.Cfg.MongoUri if mongoURI == "" { @@ -24,15 +29,10 @@ func InitDb(ctx context.Context) error { } MongoClient, err = mongo.Connect(ctx, options.Client().ApplyURI(mongoURI)) - if err != nil { - return err - } - - err = EnsureUsers(ctx) return err } -func CloseDb(ctx context.Context) error { +func CloseDB(ctx context.Context) error { return MongoClient.Disconnect(ctx) } @@ -143,6 +143,14 @@ func GetDevice(ctx context.Context, id string) (model.Device, error) { } func GetDeviceCredentials(ctx context.Context, id string) (model.DeviceCredentials, error) { + cacheEntry := credentialsCache.Get(id) + if cacheEntry != nil { + creds, ok := cacheEntry.(model.DeviceCredentials) + if ok { + return creds, nil + } + } + collection := MongoClient.Database("trackeroo-backend").Collection("devices") _ctx := ctx if _ctx == nil { @@ -161,10 +169,16 @@ func GetDeviceCredentials(ctx context.Context, id string) (model.DeviceCredentia DeviceType: device.DeviceType, PrivateKey: device.PrivateKey, } + credentialsCache.Set(id, creds) return creds, nil } func GetDeviceKey(ctx context.Context, id string) (string, error) { + key := keysCache.Get(id) + if key != nil { + return key.(string), nil + } + collection := MongoClient.Database("trackeroo-backend").Collection("devices") _ctx := ctx if _ctx == nil { @@ -176,7 +190,9 @@ func GetDeviceKey(ctx context.Context, id string) (string, error) { if err != nil { return "", err } - return device.PrivateKey, nil + key = device.PrivateKey + keysCache.Set(id, key) + return key.(string), nil } func GetDevices(ctx context.Context) ([]model.Device, error) { @@ -208,6 +224,8 @@ func UpdateDevice(ctx context.Context, id string, device model.Device) error { _ctx = context.Background() } _, err := collection.UpdateOne(_ctx, bson.M{"_id": id}, bson.M{"$set": device}) + + devicesCache.Invalidate(id) return err } @@ -225,23 +243,6 @@ func DeleteDevice(ctx context.Context, id string) error { if err != nil { logger.Warning("Error deleting device: %v", err) } - return nil -} - -func EnsureUsers(ctx context.Context) error { - _ctx := ctx - if _ctx == nil { - _ctx = context.Background() - } - users := model.DefaultUsers() - for _, user := range users { - /* If the error is nil it means the user already exists */ - if _, err := GetUserByName(_ctx, user.Username); err == nil { - continue - } - if err := InsertUser(_ctx, user); err != nil { - return err - } - } + devicesCache.Invalidate(id) return nil } diff --git a/trackeroo-backend/src/internal/service/login.go b/trackeroo-backend/src/internal/service/login.go index b81065c2..9f556ce1 100644 --- a/trackeroo-backend/src/internal/service/login.go +++ b/trackeroo-backend/src/internal/service/login.go @@ -1,3 +1,4 @@ +// Package service provides access to DB, cache, login validation and redis. package service import ( @@ -15,13 +16,9 @@ func ValidateLogin(ctx context.Context, data model.Login) bool { var user model.User var err error if user, err = GetUserByName(ctx, data.Username); err != nil { - logger.Error("Failed to get user by name: %v", err) return false } logger.Debug("Checking login information for: %s", user.Username) err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(data.Password)) - if err != nil { - return false - } - return true + return err == nil } diff --git a/trackeroo-backend/src/internal/service/mqtt.go b/trackeroo-backend/src/internal/service/mqtt.go new file mode 100644 index 00000000..e58c7e5c --- /dev/null +++ b/trackeroo-backend/src/internal/service/mqtt.go @@ -0,0 +1,118 @@ +package service + +import ( + "fmt" + "strings" + "sync/atomic" + "time" + "trackeroo-backend/internal/logger" + + pahoMqtt "github.com/eclipse/paho.mqtt.golang" +) + +type TdmClient struct { + client pahoMqtt.Client + broker string + heartbeat int + port string + clientID string + username string + password string + cleanSession bool + topicData string + running atomic.Bool +} + +func NewTdmClient() *TdmClient { + username := GetenvOrDefault("MQTT_USERNAME", "admin") + password := GetenvOrDefault("MQTT_PASSWORD", "admin123") + mqttHost := GetenvOrDefault("MQTT_BROKER", "rabbitmq") + mqttPort := GetenvOrDefault("MQTT_PORT", "1883") + tdmClient := new(TdmClient) + tdmClient.broker = mqttHost + tdmClient.port = mqttPort + tdmClient.clientID = GetenvOrDefault("POD_NAME", fmt.Sprintf("trackeroo-%d", time.Now().UnixNano())) + tdmClient.username = username + tdmClient.password = password + tdmClient.cleanSession = true + tdmClient.heartbeat = 10 + tdmClient.topicData = "j/data/" + return tdmClient +} + +func (t *TdmClient) run() { + t.initClient() + for t.running.Load() { + for !t.client.IsConnected() { + t.initClient() + err := t.connect() + if err != nil { + logger.Error("Cannot connect, %v", err) + time.Sleep(time.Second * 2) + } + } + qmSize, _ := Size("data") + if qmSize > 0 { + id, data, _ := DequeueData("data") + if str, ok := data.(string); ok { + devID, tagPayload, _ := strings.Cut(string(str), "/") + tag, payload, _ := strings.Cut(string(tagPayload), "/") + for err := t.Publish(devID, tag, payload); err != nil; { + logger.Error("Cannot publish %v: %v. Retrying...", payload, err) + time.Sleep(1 * time.Second) + } + Ack("data", id) + logger.Debug("Published %v!", str) + } else { + logger.Error("Cannot publish data != string -> %v", data) + Ack("data", id) + } + + } + time.Sleep(time.Millisecond * 100) + } +} + +func (t *TdmClient) connect() error { + token := t.client.Connect() + for !token.WaitTimeout(3 * time.Second) { + } + return token.Error() +} + +func (t *TdmClient) initClient() { + opts := pahoMqtt.NewClientOptions() + logger.Debug("Connecting to MQTT broker %s", fmt.Sprintf("mqtt://%s:%s", t.broker, t.port)) + opts.AddBroker(fmt.Sprintf("mqtt://%s:%s", t.broker, t.port)) + opts.SetUsername(t.username) + opts.SetPassword(t.password) + opts.SetClientID(t.clientID) + opts.SetKeepAlive(time.Duration(t.heartbeat) * time.Second) + opts.SetPingTimeout(time.Duration(t.heartbeat) * time.Second) + opts.SetAutoReconnect(true) + opts.SetConnectionLostHandler(func(c pahoMqtt.Client, err error) { + logger.Error("MQTT Connection lost: %v", err) + }) + opts.SetOnConnectHandler(func(c pahoMqtt.Client) { + logger.Info("Connected to the MQTT broker!") + }) + t.client = pahoMqtt.NewClient(opts) +} + +func (t *TdmClient) Publish(devID, tag, payload string) error { + topic := t.topicData + devID + "/" + tag + token := t.client.Publish(topic, byte(1), true, payload) + token.Wait() + return token.Error() +} + +func (t *TdmClient) Start() { + t.running.Store(true) + go t.run() + logger.Debug("Started MQTT goroutine") +} + +func (t *TdmClient) Stop() { + t.running.Store(false) + logger.Debug("Stopped MQTT service") +} diff --git a/trackeroo-backend/src/internal/service/pq.go b/trackeroo-backend/src/internal/service/pq.go new file mode 100644 index 00000000..70c322eb --- /dev/null +++ b/trackeroo-backend/src/internal/service/pq.go @@ -0,0 +1,237 @@ +package service + +import ( + "database/sql" + "fmt" + "os" + "sync" + "time" + "trackeroo-backend/internal/logger" + + _ "modernc.org/sqlite" +) + +type PQ struct { + dbCon *sql.DB + dbLock sync.Mutex +} + +var queues map[string]*PQ = map[string]*PQ{} + +func InitQueue(name string) { + queueDir := "status/queues/" + name + queueFile := queueDir + "/" + name + ".db" + err := os.MkdirAll(queueDir, 0777) + if err != nil { + logger.Error("Cannot open data queue, %s", err) + } + + if _, err := os.Stat(queueFile); os.IsNotExist(err) { + dbFile, _ := os.Create(queueFile) + dbFile.Close() + } + queue := new(PQ) + queue.dbLock.Lock() + defer queue.dbLock.Unlock() + queue.dbCon, err = sql.Open("sqlite", queueFile) + if err != nil { + logger.Error("Cannot open database, %s", err) + } + queue.dbCon.Exec("BEGIN TRANSACTION") + queue.dbCon.Exec("CREATE TABLE IF NOT EXISTS " + name + "_queue (id INTEGER PRIMARY KEY AUTOINCREMENT, data BLOB, status INTEGER)") + queue.dbCon.Exec("COMMIT") + queues[name] = queue +} + +func EnqueueData(name, devID, tag, payload string) { + p := queues[name] + if p == nil { + logger.Error("Queue %s does not exists", name) + return + } + data := devID + "/" + tag + "/" + payload + + insertQuery := "INSERT INTO " + name + "_queue(data, status) VALUES(?,?);" + p.dbLock.Lock() + + defer p.dbLock.Unlock() + + tx, err := p.dbCon.Begin() + if err != nil { + logger.Error("%s", err) + } + stm, err := tx.Prepare(insertQuery) + if err != nil { + logger.Error("%s", err) + } + _, err = stm.Exec(data, 0) + // _, err = p.dbCon.Exec(insertQuery, data, 0) + if err != nil { + tx.Rollback() + logger.Error("%s", err) + return + } + stm.Close() + err = tx.Commit() + if err != nil { + logger.Error("logger.Error committing, %s", err) + } +} + +func DequeueData(name string) (int, any, error) { + dequeueQuery := "SELECT * FROM " + name + "_queue WHERE status = 0 ORDER BY id;" + updateStatusQuery := "UPDATE " + name + "_queue SET status = 1 WHERE id = ?;" + p := queues[name] + if p == nil { + logger.Error("Queue %s does not exists", name) + return -1, nil, fmt.Errorf("queue does not exists") + } + var id int + var data any + var status int + p.dbLock.Lock() + defer p.dbLock.Unlock() + + tx, err := p.dbCon.Begin() + if err != nil { + logger.Error("logger.Error beginning the transaction, %s", err) + return -1, nil, fmt.Errorf("cannot start the transaction") + } + stm, err := tx.Prepare(dequeueQuery) + if err != nil { + logger.Error("logger.Error dequeuing, %s", err) + } + // res := tx.QueryRow(dequeueQuery) + res := stm.QueryRow() + err = res.Scan(&id, &data, &status) + if err != nil { + logger.Error("logger.Error reading the row, %s", err) + id = -1 + } + stm.Close() + if id == -1 { + err = tx.Rollback() + if err != nil { + logger.Error("logger.Error rolling back, %s", err) + } + return id, nil, nil + } + stm, err = tx.Prepare(updateStatusQuery) + if err != nil { + logger.Error("logger.Error error preparing the update, %s", err) + tx.Rollback() + return -1, nil, fmt.Errorf("cannot dequeue") + } + _, err = stm.Exec(id) + stm.Close() + if err != nil { + logger.Error("logger.Error updating the status, %s", err) + tx.Rollback() + return -1, nil, fmt.Errorf("cannot update status after dequeue") + } + + err = tx.Commit() + if err != nil { + tx.Rollback() + return -1, nil, fmt.Errorf("error committing, %s", err) + } + return id, data, nil +} + +func setStatus(name string, id int, status int) error { + updateStatusQuery := "UPDATE " + name + "_queue SET status = ? WHERE id = ?;" + p := queues[name] + if p == nil { + logger.Error("Queue %s does not exists", name) + return fmt.Errorf("queue does not exists") + } + p.dbLock.Lock() + defer p.dbLock.Unlock() + tx, err := p.dbCon.Begin() + if err != nil { + logger.Error("logger.Error starting the transaction, %s", err) + } + stm, err := tx.Prepare(updateStatusQuery) + if err != nil { + logger.Error("logger.Error preparing the update, %s", err) + } + _, err = stm.Exec(status, id) + if err != nil { + logger.Error("logger.Error updating the status, %s", err) + tx.Rollback() + stm.Close() + return fmt.Errorf("cannot set the status") + } + + stm.Close() + err = tx.Commit() + if err != nil { + logger.Error("logger.Error committing, %s", err) + } + return nil +} + +func Size(name string) (int, error) { + sizeQuery := "SELECT COUNT(*) FROM " + name + "_queue WHERE status = 0 ORDER BY id;" + p := queues[name] + if p == nil { + logger.Error("Queue %s does not exists", name) + return -1, fmt.Errorf("queue does not exists") + } + p.dbLock.Lock() + defer p.dbLock.Unlock() + tx, err := p.dbCon.Begin() + if err != nil { + logger.Error("logger.Error starting the transaction, %s", err) + } + stm, _ := tx.Prepare(sizeQuery) + var id int + row := stm.QueryRow() + err = row.Scan(&id) + tx.Commit() + if err != nil { + return -1, fmt.Errorf("error reading the size, %s", err) + } + return id, nil +} + +func Ack(name string, id int) error { + return setStatus(name, id, 5) +} + +func Unack(name string, id int) error { + return setStatus(name, id, 0) +} + +func QueuesCleanUp() { + logger.Debug("Started queues cleanup") + for { + for queueName := range queues { + // if condition, close db, reopen + logger.Debug("Cleaning %s", queueName) + queue := queues[queueName] + queue.dbLock.Lock() + tx, err := queue.dbCon.Begin() + if err != nil { + logger.Error("logger.Error starting the transaction, retrying later: %s", err) + queue.dbLock.Unlock() + continue + } + _, err = tx.Exec("DELETE FROM " + queueName + "_queue WHERE status = 5") + if err != nil { + logger.Error("Deleting acked rows, %s", err) + } + + tx.Commit() + _, err = queue.dbCon.Exec("VACUUM") + if err != nil { + logger.Error("logger.Error executing VACUUM: %s", err) + } + queue.dbCon.Close() + queue.dbCon, _ = sql.Open("sqlite", "status/queues/"+queueName+"/"+queueName+".db") + queue.dbLock.Unlock() + logger.Debug("Cleaned %s", queueName) + } + time.Sleep(time.Second * 30) + } +} diff --git a/trackeroo-backend/src/internal/service/rabbit.go b/trackeroo-backend/src/internal/service/rabbit.go new file mode 100644 index 00000000..7b271c39 --- /dev/null +++ b/trackeroo-backend/src/internal/service/rabbit.go @@ -0,0 +1,160 @@ +// Package service provides access to DB, cache, login validation and redis. +package service + +import ( + "context" + "fmt" + "strings" + "time" + "trackeroo-backend/internal/logger" + + "github.com/streadway/amqp" +) + +type ConnectionEvent struct { + Timestamp time.Time + User string + Name string +} + +func getClientIP(connName string) string { + parts := strings.Split(connName, "->") + if len(parts) != 2 { + return "" + } + client := strings.TrimSpace(parts[1]) + ip := strings.Split(client, ":")[0] + return ip +} + +func StartRabbitWatcher(ctx context.Context) { + go runRabbitWatcher(ctx) +} + +func runRabbitWatcher(ctx context.Context) { + username := GetenvOrDefault("RABBITMQ_USERNAME", "apps") + password := GetenvOrDefault("RABBITMQ_PASSWORD", "apps") + host := GetenvOrDefault("RABBITMQ_HOST", "rabbitmq") + port := GetenvOrDefault("RABBITMQ_PORT", "5672") + rabbitEndpoint := fmt.Sprintf("amqp://%s:%s@%s:%s/", username, password, host, port) + + for { + select { + case <-ctx.Done(): + logger.Info("Stopping RabbitMQ watcher") + return + default: + } + + conn, err := amqp.Dial(rabbitEndpoint) + if err != nil { + logger.Error("Cannot connect to RabbitMQ: %v", err) + time.Sleep(5 * time.Second) + continue + } + logger.Info("Connected to RabbitMQ") + + ch, err := conn.Channel() + if err != nil { + logger.Error("Failed to open channel: %v", err) + _ = conn.Close() + time.Sleep(5 * time.Second) + continue + } + + // Declare queue + q, err := ch.QueueDeclare( + "device_conn_events", + true, // durable + false, // auto-delete + false, // exclusive + false, // no-wait + nil, + ) + if err != nil { + logger.Error("Queue declare failed: %v", err) + _ = ch.Close() + _ = conn.Close() + time.Sleep(5 * time.Second) + continue + } + + // Bind queue to connection events + if err := ch.QueueBind( + q.Name, + "connection.*", + "amq.rabbitmq.event", + false, + nil, + ); err != nil { + logger.Error("Queue bind failed: %v", err) + _ = ch.Close() + _ = conn.Close() + time.Sleep(5 * time.Second) + continue + } + + msgs, err := ch.Consume( + q.Name, + "", + false, // auto-ack + false, // exclusive + false, // no-local + false, // no-wait + nil, + ) + if err != nil { + logger.Error("Consume failed: %v", err) + _ = ch.Close() + _ = conn.Close() + time.Sleep(5 * time.Second) + continue + } + + // Process messages until context cancel OR channel closes + loop: + for { + select { + case <-ctx.Done(): + logger.Info("Context canceled, shutting down RabbitMQ watcher") + _ = ch.Close() + _ = conn.Close() + return + case msg, ok := <-msgs: + if !ok { + logger.Warning("RabbitMQ channel closed, reconnecting...") + _ = ch.Close() + _ = conn.Close() + time.Sleep(5 * time.Second) + break loop + } + msg.Ack(false) + + // Parse and handle event + + user, ok := msg.Headers["user"].(string) + if !ok { + continue + } + name, ok := msg.Headers["name"].(string) + if !ok { + name = "" + } + ev := ConnectionEvent{ + Timestamp: msg.Timestamp, + User: user, + Name: name, + } + + switch msg.RoutingKey { + case "connection.created": + logger.Debug("Device %s connected from %s", ev.User, ev.Name) + SetDeviceStatus(ctx, ev.User, getClientIP(ev.Name), ev.Timestamp, true) + case "connection.closed": + logger.Debug("Device %s disconnected", ev.User) + SetDeviceStatus(ctx, ev.User, getClientIP(ev.Name), ev.Timestamp, false) + } + } + } + } +} diff --git a/trackeroo-backend/src/internal/service/token.go b/trackeroo-backend/src/internal/service/token.go index 25994b0b..82637c7f 100644 --- a/trackeroo-backend/src/internal/service/token.go +++ b/trackeroo-backend/src/internal/service/token.go @@ -21,7 +21,7 @@ func GenerateJWT(user model.User) (string, jwt.MapClaims, error) { } jwtKey := getKey("users_key") if jwtKey == nil { - return "", nil, fmt.Errorf("Failed to get jwt key") + return "", nil, fmt.Errorf("failed to get jwt key") } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenString, err := token.SignedString(jwtKey) @@ -35,15 +35,14 @@ func GenerateJWT(user model.User) (string, jwt.MapClaims, error) { func ValidateUserJWT(authHeader string, role string) (bool, error) { parts := strings.SplitN(authHeader, " ", 2) if len(parts) != 2 { - return false, fmt.Errorf("Invalid Authorization header format") + return false, fmt.Errorf("invalid Authorization header format") } - token, err := jwt.Parse(parts[1], func(token *jwt.Token) (interface{}, error) { + token, err := jwt.Parse(parts[1], func(token *jwt.Token) (any, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return getKey("users_key"), nil }) - if err != nil { return false, err } @@ -67,23 +66,41 @@ func ValidateDeviceJWT(encodedKey string, tokenString string) (bool, jwt.MapClai } return secret, nil }) - if err != nil { return false, nil, err } if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { return true, claims, nil - } return false, nil, fmt.Errorf("invalid token") } +func fileExists(path string) bool { + _, err := os.Stat(path) + if err == nil { + return true + } + if os.IsNotExist(err) { + return false + } + return false +} func getKey(key string) []byte { - jwtKey, err := os.ReadFile(fmt.Sprintf("/run/secrets/%s", key)) - if err != nil { - return nil + run_path := fmt.Sprintf("/run/secrets/%s", key) + var jwtKey []byte + var err error + if fileExists(run_path) { + jwtKey, err = os.ReadFile(run_path) + if err != nil { + return nil + } + } else { + jwtKey = []byte(GetenvOrDefault("USERS_KEY", "")) + if len(jwtKey) == 0 { + return nil + } } return jwtKey } diff --git a/trackeroo-backend/src/internal/service/utils.go b/trackeroo-backend/src/internal/service/utils.go new file mode 100644 index 00000000..8b2abb0c --- /dev/null +++ b/trackeroo-backend/src/internal/service/utils.go @@ -0,0 +1,14 @@ +// Package service provides access to DB, cache, login validation and redis. +package service + +import ( + "os" +) + +func GetenvOrDefault(key, def string) string { + val := os.Getenv(key) + if val == "" { + return def + } + return val +} diff --git a/trackeroo-backend/src/version.yml b/trackeroo-backend/src/version.yml new file mode 100644 index 00000000..2ef3d523 --- /dev/null +++ b/trackeroo-backend/src/version.yml @@ -0,0 +1 @@ +version: 1.0.0 diff --git a/tracky/.gitignore b/tracky/.gitignore new file mode 100644 index 00000000..48a00e84 --- /dev/null +++ b/tracky/.gitignore @@ -0,0 +1,2 @@ +trk-* +docker-compose.yml diff --git a/tracky/docker-compose.yml b/tracky/docker-compose.yml deleted file mode 100644 index dca83ef1..00000000 --- a/tracky/docker-compose.yml +++ /dev/null @@ -1,361 +0,0 @@ -services: - trk-185ed4ee37ca7c3effa0a5b2: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37ca7c3effa0a5b2:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37ccac29287f5bfc: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37ccac29287f5bfc:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37ccdc2145feb671: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37ccdc2145feb671:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37ccfde9d6c0d6e8: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37ccfde9d6c0d6e8:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37cd2182bfe59e58: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37cd2182bfe59e58:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37cd490baea45bda: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37cd490baea45bda:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37cd6ed388af77cf: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37cd6ed388af77cf:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37cd9c9ae6661e2a: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37cd9c9ae6661e2a:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37cdc28785bce7c3: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37cdc28785bce7c3:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37cdeb517d0f7754: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37cdeb517d0f7754:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37ce0c720dd6b6a7: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37ce0c720dd6b6a7:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37ce2fdf6aa5b0c2: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37ce2fdf6aa5b0c2:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37ce5573f69f6d9a: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37ce5573f69f6d9a:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37ce7a0dfb8b1f3e: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37ce7a0dfb8b1f3e:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37ceb441c570927d: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37ceb441c570927d:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37cee05aaace21f4: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37cee05aaace21f4:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37cf0317a6b1369e: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37cf0317a6b1369e:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37cf279576ae7859: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37cf279576ae7859:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37cf4ae833197cd7: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37cf4ae833197cd7:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37cf703dc5aaff65: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37cf703dc5aaff65:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37cf9e30f1e863d3: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37cf9e30f1e863d3:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37cfc76e9abfc570: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37cfc76e9abfc570:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37cff0467daf9583: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37cff0467daf9583:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37d0145f6f8c7cbc: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37d0145f6f8c7cbc:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37d03b0718bd5337: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37d03b0718bd5337:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37d05c72bf5fa09b: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37d05c72bf5fa09b:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37d07d379e9bbf9a: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37d07d379e9bbf9a:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37d0a1a3e172b57e: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37d0a1a3e172b57e:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37d0d4ffb88e9b5d: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37d0d4ffb88e9b5d:/app/credentials - network_mode: host - restart: 'no' - trk-185ed4ee37d10d5563bdc108: - build: - context: src - dockerfile: Dockerfile - environment: - GEOCODING_SERVICE_URL: http://localhost:8081 - ROUTING_SERVICE_URL: http://localhost:5000 - PUBLISH_PERIOD: '200' - volumes: - - ./trk-185ed4ee37d10d5563bdc108:/app/credentials - network_mode: host - restart: 'no' diff --git a/tracky/geo-services/docker-compose.yml b/tracky/geo-services/docker-compose.yml index 50f974f6..a8972f59 100644 --- a/tracky/geo-services/docker-compose.yml +++ b/tracky/geo-services/docker-compose.yml @@ -3,7 +3,7 @@ services: image: mediagis/nominatim:4.4 container_name: nominatim ports: - - "8081:8080" + - "8082:8080" environment: - PBF_URL=https://download.geofabrik.de/europe/italy/centro-latest.osm.pbf - REPLICATION_URL=https://download.geofabrik.de/europe/italy/centro-updates diff --git a/tracky/make_compose.py b/tracky/make_compose.py index 487a9c14..3af0fa77 100644 --- a/tracky/make_compose.py +++ b/tracky/make_compose.py @@ -1,4 +1,5 @@ import os +import random import sys from typing import Dict, List, Optional, Union from pydantic import BaseModel @@ -65,7 +66,7 @@ def create_compose(credentials: List[Dict]): """ compose = DockerCompose(services={} )#, include=["geo-services/docker-compose.yml"]) print("SELECT * FROM ( VALUES ") - for cred in credentials: + for i, cred in enumerate(credentials): # print(f"Processing device {cred}") # continue compose.services[cred["id"]] = ServiceConfig( @@ -74,19 +75,25 @@ def create_compose(credentials: List[Dict]): "dockerfile": "Dockerfile" }, environment={ - "GEOCODING_SERVICE_URL": "http://localhost:8081", + "GEOCODING_SERVICE_URL": "http://localhost:8082", "ROUTING_SERVICE_URL": "http://localhost:5000", - "PUBLISH_PERIOD": "200" + "PUBLISH_PERIOD": "500", + "PIRATE": "true" if random.randint(1, 100) < 10 else "false" }, # depends_on=["nominatim", "osrm"], network_mode="host", volumes=[f"./{cred["id"]}:/app/credentials"], restart="no", ) + # if compose.services[cred["id"]].environment["PIRATE"] == "true": + # print(f"{cred['id']} is a pirate 🏴‍☠️") os.makedirs(f"{cred['id']}", exist_ok=True) with open(f"{cred['id']}/tdevice.json", "w") as f: json.dump(cred, f, indent=2) - print(f"('{cred['name']}', '{cred['id']}'),") + if i == len(credentials) - 1: + print(f"('{cred['name']}', '{cred['id']}')") + else: + print(f"('{cred['name']}', '{cred['id']}'),") print(") AS t (__text, __value)") with open("docker-compose.yml", "w") as f: compose_dict = compose.model_dump(exclude_none=True) @@ -110,7 +117,7 @@ def main(): print(f"Error fetching credentials: {e}") sys.exit(1) - create_compose(credentials) + create_compose(sorted(credentials, key=lambda x: x["name"])) if __name__ == "__main__": main() diff --git a/tracky/src/firmware.go b/tracky/src/firmware.go index 8e4740ec..60e92b39 100644 --- a/tracky/src/firmware.go +++ b/tracky/src/firmware.go @@ -25,14 +25,21 @@ type FoodSensors struct { } type Payload struct { - TS int64 `json:"ts"` - Speed float64 `json:"speed"` - Position trackeroo.Coordinate `json:"position"` - DeviceType string `json:"device_type"` - Status string `json:"status"` - Sensors any `json:"sensors,omitempty"` - Start trackeroo.Coordinate `json:"start"` - End trackeroo.Coordinate `json:"end"` + TS int64 `json:"ts"` + Speed float64 `json:"speed"` + SpeedLimit float64 `json:"speed_limit"` + SpeedStats map[string]any `json:"speed_stats"` + Position trackeroo.Coordinate `json:"position"` + DeviceName string `json:"device_name"` + DeviceType string `json:"device_type"` + DeviceID string `json:"device_id"` + Status string `json:"status"` + Sensors any `json:"sensors,omitempty"` + Start trackeroo.Coordinate `json:"start"` + End trackeroo.Coordinate `json:"end"` + InstantConsumption float64 `json:"instant_consumption"` + ConsumptionStats map[string]any `json:"consumption_stats"` + DeltaDistance float64 `json:"delta_distance"` } func normalValuablesData(position trackeroo.DrivePosition) ValuableSensors { @@ -48,7 +55,7 @@ func normalValuablesData(position trackeroo.DrivePosition) ValuableSensors { func robberyValuablesData() ValuableSensors { return ValuableSensors{ Alarm: true, - Vibration: rand.Float64() * 300, + Vibration: 100 + rand.Float64()*300, RearHatchOpen: true, FrontHatchOpen: rand.Float32() < 0.5, Collision: true, @@ -109,44 +116,60 @@ func Loop() { } } lastPublish := time.Now() - + isPirate := os.Getenv("PIRATE") == "true" || os.Getenv("PIRATE") == "1" + trackeroo.Info("Is pirate: %t", isPirate) lastStatus := "" lastEnd := "" normalRun := true + deltaDistance := 0.0 + speedVar := trackeroo.NewStatVar[float64]() + consumptionVar := trackeroo.NewStatVar[float64]() for { + trackeroo.Info("Getting route from %s", lastEnd) route := trackeroo.GetRoute(lastEnd) trackeroo.Info("Route: %+v", route) - lastEnd = route[len(route)-1] - drivingSimulator, err := trackeroo.NewDrivingSimulator(routingService, route, 60, 100) + drivingSimulator, err := trackeroo.NewDrivingSimulator(routingService, route, 60, 100, isPirate, creds.DeviceType) if err != nil { trackeroo.Error("Error initializing driving simulator %v", err) continue } + lastEnd = route[len(route)-1] positionChan := drivingSimulator.SimulateDrive() if rand.Float64() < 0.05 || os.Getenv("NORMAL_RUN") == "false" { normalRun = false } for position := range positionChan { + deltaDistance += position.Distance + speedVar.Add(position.Speed) + consumptionVar.Add(position.Consumption) if time.Since(lastPublish) > pubPeriod || lastStatus != position.Status { lastStatus = position.Status payload := Payload{ TS: position.Timestamp.Unix(), Speed: position.Speed, + SpeedStats: speedVar.Get(), + SpeedLimit: position.SpeedLimit, + DeviceName: creds.Name, DeviceType: creds.DeviceType, Position: trackeroo.Coordinate{ Lat: position.Lat, Lng: position.Lng, }, - Status: position.Status, - Start: position.Start, - End: position.End, + Status: position.Status, + Start: position.Start, + End: position.End, + InstantConsumption: position.Consumption, + ConsumptionStats: consumptionVar.Get(), + DeltaDistance: deltaDistance, } + deltaDistance = 0 switch deviceType { case trackeroo.VALUABLES: vsensors := normalValuablesData(position) - if !normalRun { + /* For the sake of simplicity the robbery happens when the vehicle arrives */ + if !normalRun && position.Status == trackeroo.ARRIVED { vsensors = robberyValuablesData() } payload.Sensors = vsensors @@ -169,5 +192,9 @@ func Loop() { } /* Routing terminated, waiting before next route */ time.Sleep(time.Second * 120) + if !normalRun { + /* Wait some more */ + time.Sleep(time.Second * 120) + } } } diff --git a/tracky/src/trackeroo/agent.go b/tracky/src/trackeroo/agent.go index aa7d685a..fbc5c52f 100755 --- a/tracky/src/trackeroo/agent.go +++ b/tracky/src/trackeroo/agent.go @@ -39,7 +39,7 @@ func (a *Agent) run() { } Ack("data", id) - Info("Published %v!", str) + Debug("Published %v!", str) } else { Error("Cannot publish data != string -> %v", data) diff --git a/tracky/src/trackeroo/driving.go b/tracky/src/trackeroo/driving.go index be30a54e..30c977f0 100644 --- a/tracky/src/trackeroo/driving.go +++ b/tracky/src/trackeroo/driving.go @@ -19,6 +19,13 @@ const ( ACCELERATING = "accelerating" ) +var consumptionMultipliers = map[string]float32{ + "valuable": 1.3, // heavier, armored vans/trucks + "food": 1.1, // refrigerated transport adds load + "private_transport": 1.0, // baseline + "public_transport": 1.8, // buses have much higher consumption +} + type Coordinate struct { Lat float64 `json:"lat"` Lng float64 `json:"lon"` @@ -64,11 +71,14 @@ type OSRMResponse struct { type DrivePosition struct { Coordinate - Timestamp time.Time - Speed float64 // km/h - Status string // "driving", "stopped", "slowing", "accelerating" - Start Coordinate - End Coordinate + Timestamp time.Time + Speed float64 // km/h + SpeedLimit float64 + Status string // "driving", "stopped", "slowing", "accelerating" + Start Coordinate + End Coordinate + Consumption float64 + Distance float64 } type RoutingService struct { @@ -182,21 +192,36 @@ func (rs *RoutingService) GetRoute(from, to *Coordinate) ([]RouteSegment, error) return segments, nil } +func getConsumption(speed float64, devType string) float64 { + if speed == 0 { + return 0.0 + } + threshold := 80.0 + consumption := 5 + 60.7/speed + if speed >= threshold { + consumption += 5 * math.Log(speed/threshold) + } + return consumption * float64(consumptionMultipliers[devType]) +} + // DrivingSimulator simulates realistic car driving type DrivingSimulator struct { Route []RouteSegment DefaultAverageSpeed float64 // km/h SpeedVariation float64 // percentage (0.0-1.0) StopProbability float64 // probability of stopping per segment (0.0-1.0) + DevType string StopDuration struct { Min time.Duration Max time.Duration } UpdateInterval time.Duration + IsPirate bool rand *rand.Rand + Distance float64 } -func NewDrivingSimulator(routingService *RoutingService, waypoints []string, avgSpeed float64, updateIntervalMs int) (*DrivingSimulator, error) { +func NewDrivingSimulator(routingService *RoutingService, waypoints []string, avgSpeed float64, updateIntervalMs int, isPirate bool, devType string) (*DrivingSimulator, error) { route := make([]RouteSegment, 0) for i := 0; i < len(waypoints)-1; i++ { start, err := routingService.Geocode(waypoints[i]) @@ -217,7 +242,7 @@ func NewDrivingSimulator(routingService *RoutingService, waypoints []string, avg return &DrivingSimulator{ Route: route, DefaultAverageSpeed: avgSpeed, - SpeedVariation: 0.2, // 20% speed variation + SpeedVariation: 0.03, // 3% speed variation StopProbability: 0.05, // 5% chance of stopping per segment StopDuration: struct { Min time.Duration @@ -228,13 +253,14 @@ func NewDrivingSimulator(routingService *RoutingService, waypoints []string, avg }, UpdateInterval: time.Duration(updateIntervalMs) * time.Millisecond, rand: rand.New(rand.NewSource(time.Now().UnixNano())), + IsPirate: isPirate, + DevType: devType, }, nil } // SimulateDrive simulates driving along a route and sends positions through a channel func (ds *DrivingSimulator) SimulateDrive() <-chan DrivePosition { positionChan := make(chan DrivePosition, 100) - lastCoord := ds.Route[len(ds.Route)-1].Coordinates[len(ds.Route[len(ds.Route)-1].Coordinates)-1] lastSpeed := 0.0 go func() { @@ -246,6 +272,8 @@ func (ds *DrivingSimulator) SimulateDrive() <-chan DrivePosition { currentTime := time.Now() stopCompensation := 1 + consumptionCompensation := 1.0 + speedLimit := 0.0 for _, segment := range ds.Route { for i := 0; i < len(segment.Coordinates)-1; i++ { currentPos := segment.Coordinates[i] @@ -258,12 +286,15 @@ func (ds *DrivingSimulator) SimulateDrive() <-chan DrivePosition { if i == 0 && segment.ShouldStop { // Send stopped position positionChan <- DrivePosition{ - Coordinate: currentPos, - Timestamp: currentTime, - Speed: 0, - Status: STOPPED, - Start: ds.Route[0].Coordinates[0], - End: lastCoord, + Coordinate: currentPos, + Timestamp: currentTime, + Speed: 0, + SpeedLimit: speedLimit, + Status: STOPPED, + Start: ds.Route[0].Coordinates[0], + End: lastCoord, + Consumption: 0, + Distance: 0, } // Random stop duration @@ -272,6 +303,7 @@ func (ds *DrivingSimulator) SimulateDrive() <-chan DrivePosition { time.Sleep(stopTime) currentTime = time.Now() stopCompensation = 3 + lastSpeed = 0 } // Calculate current speed with variation @@ -283,24 +315,33 @@ func (ds *DrivingSimulator) SimulateDrive() <-chan DrivePosition { baseSpeed /= float64(stopCompensation) stopCompensation -= 1 } + speedLimit = baseSpeed speedVariation := 1.0 + (ds.rand.Float64()-0.5)*2*ds.SpeedVariation currentSpeed := baseSpeed * speedVariation + if ds.IsPirate { + currentSpeed *= 1.40 + } // Calculate time to travel this segment travelTimeHours := distance / currentSpeed segmentDuration := time.Duration(travelTimeHours * float64(time.Hour)) // Interpolate positions along the segment - steps := int(segmentDuration / ds.UpdateInterval) - if steps < 1 { - steps = 1 - } - status := DRIVING + steps := max(int(segmentDuration/ds.UpdateInterval), 1) - if lastSpeed < currentSpeed { - status = ACCELERATING - } else { - status = SLOWING + status := DRIVING + consumptionCompensation = 1 + speedDifference := math.Abs(currentSpeed - lastSpeed) + tolerance := currentSpeed * 0.05 // 5% tolerance + + if speedDifference > tolerance { + if lastSpeed < currentSpeed { + status = ACCELERATING + consumptionCompensation = 1.3 + } else { + status = SLOWING + consumptionCompensation = 0.5 + } } lastSpeed = currentSpeed @@ -310,12 +351,15 @@ func (ds *DrivingSimulator) SimulateDrive() <-chan DrivePosition { interpolatedPos := ds.interpolatePosition(currentPos, nextPos, progress) positionChan <- DrivePosition{ - Coordinate: interpolatedPos, - Timestamp: currentTime, - Speed: currentSpeed, - Status: status, - Start: ds.Route[0].Coordinates[0], - End: lastCoord, + Coordinate: interpolatedPos, + Timestamp: currentTime, + Speed: currentSpeed, + SpeedLimit: speedLimit, + Status: status, + Start: ds.Route[0].Coordinates[0], + End: lastCoord, + Consumption: getConsumption(currentSpeed, ds.DevType) * consumptionCompensation, + Distance: distance, } time.Sleep(ds.UpdateInterval) currentTime = time.Now() @@ -325,12 +369,15 @@ func (ds *DrivingSimulator) SimulateDrive() <-chan DrivePosition { // Send final position positionChan <- DrivePosition{ - Coordinate: lastCoord, - Timestamp: currentTime, - Speed: 0, - Status: ARRIVED, - Start: ds.Route[0].Coordinates[0], - End: lastCoord, + Coordinate: lastCoord, + Timestamp: currentTime, + Speed: 0, + SpeedLimit: speedLimit, + Status: ARRIVED, + Start: ds.Route[0].Coordinates[0], + End: lastCoord, + Consumption: 0, + Distance: 0, } }() diff --git a/tracky/src/trackeroo/mqtt.go b/tracky/src/trackeroo/mqtt.go index 717c89ca..275f4830 100755 --- a/tracky/src/trackeroo/mqtt.go +++ b/tracky/src/trackeroo/mqtt.go @@ -119,7 +119,7 @@ func (t *TdmClient) handleDnMsg(client pahoMqtt.Client, msg pahoMqtt.Message) { Error("Missing value") t.replyJob(key[1:], map[string]any{"error": "Missing args"}) } - args, ok := value["args"].(map[string]any) + args, _ := value["args"].(map[string]any) t.handleJobRequest(key[1:], args) } } @@ -166,7 +166,6 @@ func (t *TdmClient) initClient() { opts.SetTLSConfig(&tlsConfig) } else { opts.AddBroker(fmt.Sprintf("mqtt://%s:%s", t.broker, strconv.Itoa(t.port))) - } opts.SetUsername(t.clientId) opts.SetKeepAlive(time.Duration(t.heartbeat) * time.Second) @@ -177,11 +176,11 @@ func (t *TdmClient) initClient() { }) opts.SetOnConnectHandler(func(c pahoMqtt.Client) { Info("Connected!") - t.client.Subscribe(t.topicDn, byte(1), t.handleDnMsg) - t.requestStatus() - t.requestTime() - t.sendOsInfo() - t.sendManifest() + // t.client.Subscribe(t.topicDn, byte(1), t.handleDnMsg) + // t.requestStatus() + // t.requestTime() + // t.sendOsInfo() + // t.sendManifest() }) token, err := GetToken(t.creds.PrivateKey, 200, 200, t.creds.ID) // fmt.Println(token) @@ -244,7 +243,7 @@ func (t *TdmClient) sendManifest() { "reset", "restart", } - for k, _ := range Jobs { + for k := range Jobs { jobs = append(jobs, k) } value := map[string]any{ diff --git a/tracky/src/trackeroo/utils.go b/tracky/src/trackeroo/utils.go index a6caa0ad..ee205674 100755 --- a/tracky/src/trackeroo/utils.go +++ b/tracky/src/trackeroo/utils.go @@ -5,6 +5,54 @@ import ( "time" ) +type Number interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 | + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | + ~float32 | ~float64 +} + +type StatVar[T Number] struct { + Avg float64 `json:"avg"` + Min T `json:"min"` + Max T `json:"max"` + Count int `json:"count"` +} + +func NewStatVar[T Number]() StatVar[T] { + return StatVar[T]{} +} + +func (sv *StatVar[T]) Add(value T) { + if sv.Count == 0 { + sv.Avg = 0.0 + sv.Min = value + sv.Max = value + } else { + sv.Avg += (float64(value) - sv.Avg) / float64(sv.Count+1) + if value < sv.Min { + sv.Min = value + } + if value > sv.Max { + sv.Max = value + } + } + sv.Count++ +} + +func (sv *StatVar[T]) Get() map[string]any { + res := map[string]any{ + "avg": sv.Avg, + "min": sv.Min, + "max": sv.Max, + "count": sv.Count, + } + sv.Avg = 0.0 + sv.Min = 0 + sv.Max = 0 + sv.Count = 0 + return res +} + func UnixTime() uint64 { return uint64(time.Now().Unix()) } diff --git a/tracky/src/tracky b/tracky/src/tracky deleted file mode 100755 index aa66d455..00000000 Binary files a/tracky/src/tracky and /dev/null differ diff --git a/images/tsdb/Dockerfile b/tsdb/Dockerfile similarity index 100% rename from images/tsdb/Dockerfile rename to tsdb/Dockerfile diff --git a/tsdb/docker-entrypoint-initdb.d/01-init.sql b/tsdb/docker-entrypoint-initdb.d/01-init.sql new file mode 100644 index 00000000..ed2570e7 --- /dev/null +++ b/tsdb/docker-entrypoint-initdb.d/01-init.sql @@ -0,0 +1,35 @@ +CREATE EXTENSION IF NOT EXISTS timescaledb; +CREATE EXTENSION IF NOT EXISTS postgis; +CREATE EXTENSION IF NOT EXISTS postgis_topology; +CREATE EXTENSION IF NOT EXISTS fuzzystrmatch; +CREATE EXTENSION IF NOT EXISTS postgis_tiger_geocoder; + +DO +$$ +BEGIN + IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'admin') THEN + CREATE ROLE admin + WITH LOGIN SUPERUSER CREATEDB CREATEROLE REPLICATION BYPASSRLS + PASSWORD 'administrator'; + END IF; +END +$$; + +-- Application role +DO +$$ +BEGIN + IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'apps') THEN + CREATE ROLE apps + WITH LOGIN PASSWORD 'apps'; + END IF; +END +$$; +GRANT ALL PRIVILEGES ON DATABASE tracker_db TO admin; + +-- TimescaleDB specific permissions +GRANT USAGE ON SCHEMA _timescaledb_catalog TO apps; +GRANT USAGE ON SCHEMA _timescaledb_config TO apps; +GRANT USAGE ON SCHEMA _timescaledb_internal TO apps; +GRANT SELECT ON ALL TABLES IN SCHEMA _timescaledb_catalog TO apps; +GRANT SELECT ON ALL TABLES IN SCHEMA _timescaledb_config TO apps; diff --git a/tsdb/docker-entrypoint-initdb.d/02-schema.sql b/tsdb/docker-entrypoint-initdb.d/02-schema.sql new file mode 100644 index 00000000..3c5f91d4 --- /dev/null +++ b/tsdb/docker-entrypoint-initdb.d/02-schema.sql @@ -0,0 +1,55 @@ +CREATE SCHEMA IF NOT EXISTS trackeroo; + +CREATE TABLE IF NOT EXISTS trackeroo.data ( + ts_unix BIGINT NOT NULL, + ts TIMESTAMPTZ NOT NULL, + dev_id TEXT NOT NULL, + insertion_time TIMESTAMPTZ DEFAULT NOW(), + tag TEXT NOT NULL, + payload JSONB NOT NULL, + PRIMARY KEY (ts, dev_id) +); + + +CREATE TABLE IF NOT EXISTS trackeroo.aggregated ( + ts_unix BIGINT NOT NULL, + ts TIMESTAMPTZ NOT NULL, + dev_id TEXT NOT NULL, + route_hash TEXT NOT NULL, + insertion_time TIMESTAMPTZ DEFAULT NOW(), + tag TEXT NOT NULL, + payload JSONB NOT NULL, + + PRIMARY KEY (ts, route_hash, dev_id) +); + +SELECT create_hypertable('trackeroo.data', 'ts', 'dev_id', 16); +SELECT create_hypertable('trackeroo.aggregated', 'ts', 'dev_id', 16); + +CREATE INDEX IF NOT EXISTS idx_trackeroo_data_dev_id ON trackeroo.data(dev_id); +CREATE INDEX IF NOT EXISTS idx_trackeroo_data_tag ON trackeroo.data(tag); +CREATE INDEX IF NOT EXISTS idx_trackeroo_data_ts ON trackeroo.data(ts); + +CREATE INDEX IF NOT EXISTS idx_trackeroo_aggregated_dev_id ON trackeroo.aggregated(dev_id); +CREATE INDEX IF NOT EXISTS idx_trackeroo_aggregated_route_hash ON trackeroo.aggregated(route_hash); +CREATE INDEX IF NOT EXISTS idx_trackeroo_aggregated_tag ON trackeroo.aggregated(tag); +CREATE INDEX IF NOT EXISTS idx_trackeroo_aggregated_ts ON trackeroo.aggregated(ts); + +SELECT add_retention_policy('trackeroo.data', INTERVAL '3 days'); +SELECT add_retention_policy('trackeroo.aggregated', INTERVAL '3 days'); + +-- Now I have to allow apps to read/write on all current tables +GRANT USAGE ON SCHEMA trackeroo TO apps; +-- +-- Grant read/write on all current tables +GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA trackeroo TO apps; + +-- Grant read/write on all sequences (needed if you ever add SERIAL/IDENTITY columns) +GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA trackeroo TO apps; + +-- Ensure future tables/sequences also inherit these permissions +ALTER DEFAULT PRIVILEGES IN SCHEMA trackeroo + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO apps; + +ALTER DEFAULT PRIVILEGES IN SCHEMA trackeroo + GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO apps; diff --git a/images/tsdb/version.yml b/tsdb/version.yml similarity index 100% rename from images/tsdb/version.yml rename to tsdb/version.yml diff --git a/trackeroo-backend/gen_mongo_dev/go.mod b/utils/gen_mongo_dev/go.mod similarity index 100% rename from trackeroo-backend/gen_mongo_dev/go.mod rename to utils/gen_mongo_dev/go.mod diff --git a/trackeroo-backend/gen_mongo_dev/main.go b/utils/gen_mongo_dev/main.go similarity index 75% rename from trackeroo-backend/gen_mongo_dev/main.go rename to utils/gen_mongo_dev/main.go index cc6d45d3..9813d73d 100644 --- a/trackeroo-backend/gen_mongo_dev/main.go +++ b/utils/gen_mongo_dev/main.go @@ -32,6 +32,26 @@ var containerNames = []string{ "confident_faraday", "cool_ohm", "cranky_ampere", "crazy_coulomb", "curious_feynman", "dazzling_maxwell", "determined_kelvin", "distracted_planck", "dreamy_euler", "eager_gauss", "ecstatic_riemann", "elastic_fourier", + "elegant_lagrange", "elated_laplace", "eloquent_leibniz", "enchanting_poincare", + "energetic_hilbert", "epic_cantor", "exciting_godel", "exotic_turing", + "fabulous_ramanujan", "faithful_hardy", "fancy_erdos", "fascinated_noether", + "fearless_galois", "fervent_abel", "flamboyant_jacobi", "focused_cauchy", + "friendly_weierstrass", "frosty_dedekind", "funny_peano", "furious_russell", + "gallant_whitehead", "gentle_church", "gifted_kleene", "goofy_markov", + "graceful_chebyshev", "great_kolmogorov", "grieving_wiener", "groovy_shannon", + "happy_nyquist", "hardcore_bell", "heartwarming_bose", "heuristic_fermi", + "hopeful_bardeen", "hungry_cooper", "hyper_shockley", "inspiring_bardeen", + "interesting_watson", "inventive_crick", "iron_franklin", "jaunty_pauling", + "jovial_mendeleev", "keen_bohr", "laughing_rutherford", "lucid_heisenberg", + "magical_dirac", "magnificent_feynman", "merry_schwinger", "modest_dyson", + "motivated_penrose", "nervous_hawking", "noble_weinberg", "nostalgic_salam", + "objective_glashow", "optimized_higgs", "original_yang", "outstanding_lee", + "patient_wu", "pedantic_pauli", "phenomenal_born", "pious_planck", + "playful_compton", "polite_millikan", "practical_michelson", "proud_morley", + "puzzled_fizeau", "quizzical_doppler", "romantic_hertz", "sad_marconi", + "serene_tesla", "sharp_edison", "silly_westinghouse", "sleepy_siemens", + "stoic_ohm", "strange_ampere", "suspicious_volta", "sweet_galvani", + "tender_faraday", "thirsty_henry", "thoughtful_weber", "thrilled_gauss", } var deviceTypes = []string{VALUABLES, FOOD, PRIVATE_TRANSPORT, PUBLIC_TRANSPORT, OTHER} @@ -150,7 +170,7 @@ func main() { rand.Seed(time.Now().UnixNano()) // Generate 5 objects by default (max 50 due to name uniqueness) - count := 30 + count := 100 if count > len(containerNames) { fmt.Printf("Warning: Requested %d objects but only %d unique names available. Using %d objects.\n", diff --git a/utils/get_status.py b/utils/get_status.py new file mode 100644 index 00000000..7dd34ff7 --- /dev/null +++ b/utils/get_status.py @@ -0,0 +1,27 @@ +import sys +import json + +import requests +import time + + +def token(): + response = requests.post(f"http://{sys.argv[1]}/login/", json={"username": "leonardo", "password": "subemelaradio"}) + return response.json()["token"] + +def main(): + if len(sys.argv) != 2: + print(f"Usage: python {sys.argv[0]} ") + sys.exit(1) + + try: + t = token() + except Exception as e: + print(f"Error fetching authenticating: {e}") + sys.exit(1) + + while True: + print(f"{json.dumps(requests.get(f'http://{sys.argv[1]}/devices/trk-18608d12c8446943ffbf2df9', headers={"Authorization": f"Bearer {t}"}).json(), indent=2)}") + time.sleep(1) +if __name__ == "__main__": + main()