Skip to content
  • Home
  • Aktuell
  • Tags
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Standard: (Kein Skin)
  • Kein Skin
Einklappen
ioBroker Logo

Community Forum

donate donate
  1. ioBroker Community Home
  2. Deutsch
  3. Skripten / Logik
  4. JavaScript
  5. Ordner auf neue Dateien überwachen

NEWS

  • UPDATE 31.10.: Amazon Alexa - ioBroker Skill läuft aus ?
    apollon77A
    apollon77
    48
    3
    8.7k

  • Monatsrückblick – September 2025
    BluefoxB
    Bluefox
    13
    1
    2.2k

  • Neues Video "KI im Smart Home" - ioBroker plus n8n
    BluefoxB
    Bluefox
    16
    1
    3.0k

Ordner auf neue Dateien überwachen

Geplant Angeheftet Gesperrt Verschoben JavaScript
37 Beiträge 8 Kommentatoren 5.4k Aufrufe 7 Watching
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • E ente34

    @tomily
    Etwa so:

    if ( seconds > 3*86400 ) {
    sendTo("email.0", "send", {
       text: 'Body',
       to: 'test@gmx.de',
       subject: 'Betreff'
    });
    
    }
    
    T Offline
    T Offline
    tomily
    schrieb am zuletzt editiert von
    #10

    @ente34
    Guten Morgen,

    herzlichen Dank. Wirkt beides plausibel. Sehr geil das hilft mir extrem.
    Könntest du mir noch verraten, wo ich die Email-Scriptszeilen einfügen soll? Innerhalb des Scriptes oben, oder in einem separaten Aufruf?

    So ist es offensichtlich falsch :-D (Habe die Emailadresse absichtlich noch nicht ausgefüllt)

    const fs = require('fs');
    const util = require('util');
    const readdir = util.promisify(fs.readdir);
    const stat = util.promisify(fs.stat);
    const lstat = util.promisify(fs.lstat);
    
    async function checkDir(dir) {
        let files = [];
        try {
            files = await readdir(dir);
           
            let newestFileAge = 0;
            let newestFile = '';
     
            for (let file of files) {
                let fullpath = dir + '/' + file;
                const s = await lstat(fullpath);
                if (s.isFile()) {
                    const stats = await stat(fullpath);
                    if (stats.mtimeMs > newestFileAge) {
                        newestFileAge = stats.mtimeMs;
                        newestFile = file;
                    }
                }
            }
     
            let seconds = (new Date().getTime() - new Date(newestFileAge).getTime()) / 1000;
            console.log(`newest file "${newestFile}" created before ${seconds} seconds`);
        } catch (e) {
            console.log(e);
        }
    }
    //################################
    if ( seconds > 3*86400 ) {
    sendTo("email.0", "send", {
       text: 'Body',
       to: 'test@gmx.de',
       subject: 'Betreff'
    });
    
    }
    //################################
    
    checkDir('/etc/test1');
    
    E 1 Antwort Letzte Antwort
    0
    • T tomily

      @ente34
      Guten Morgen,

      herzlichen Dank. Wirkt beides plausibel. Sehr geil das hilft mir extrem.
      Könntest du mir noch verraten, wo ich die Email-Scriptszeilen einfügen soll? Innerhalb des Scriptes oben, oder in einem separaten Aufruf?

      So ist es offensichtlich falsch :-D (Habe die Emailadresse absichtlich noch nicht ausgefüllt)

      const fs = require('fs');
      const util = require('util');
      const readdir = util.promisify(fs.readdir);
      const stat = util.promisify(fs.stat);
      const lstat = util.promisify(fs.lstat);
      
      async function checkDir(dir) {
          let files = [];
          try {
              files = await readdir(dir);
             
              let newestFileAge = 0;
              let newestFile = '';
       
              for (let file of files) {
                  let fullpath = dir + '/' + file;
                  const s = await lstat(fullpath);
                  if (s.isFile()) {
                      const stats = await stat(fullpath);
                      if (stats.mtimeMs > newestFileAge) {
                          newestFileAge = stats.mtimeMs;
                          newestFile = file;
                      }
                  }
              }
       
              let seconds = (new Date().getTime() - new Date(newestFileAge).getTime()) / 1000;
              console.log(`newest file "${newestFile}" created before ${seconds} seconds`);
          } catch (e) {
              console.log(e);
          }
      }
      //################################
      if ( seconds > 3*86400 ) {
      sendTo("email.0", "send", {
         text: 'Body',
         to: 'test@gmx.de',
         subject: 'Betreff'
      });
      
      }
      //################################
      
      checkDir('/etc/test1');
      
      E Offline
      E Offline
      ente34
      schrieb am zuletzt editiert von
      #11

      @tomily
      Zeile 28, wenn "seconds" zugewiesen sind

      T 1 Antwort Letzte Antwort
      0
      • E ente34

        @tomily
        Zeile 28, wenn "seconds" zugewiesen sind

        T Offline
        T Offline
        tomily
        schrieb am zuletzt editiert von
        #12

        @ente34 Vielen Dank und sorry für die späte Rückmeldung.
        Ich hatte ganz vergessen das positive Eregebnis hier zu posten :-(

        Es hat geklappt und ich bekomme jetzt eine Email, wenn die Datei zu alt ist. HERVORRAGEND.

        Nun muss ich nur dafür sorgen, dass vor dem ausführen die Laufwerke verbunden werden.
        Eine Frage hätte ich noch. Kann ich einen Trigger direkt in das Script einbauen? z.B. dass das Script immer zu einer bestimmten Tageszeit läuft?

        T 1 Antwort Letzte Antwort
        0
        • T tomily

          @ente34 Vielen Dank und sorry für die späte Rückmeldung.
          Ich hatte ganz vergessen das positive Eregebnis hier zu posten :-(

          Es hat geklappt und ich bekomme jetzt eine Email, wenn die Datei zu alt ist. HERVORRAGEND.

          Nun muss ich nur dafür sorgen, dass vor dem ausführen die Laufwerke verbunden werden.
          Eine Frage hätte ich noch. Kann ich einen Trigger direkt in das Script einbauen? z.B. dass das Script immer zu einer bestimmten Tageszeit läuft?

          T Offline
          T Offline
          tomily
          schrieb am zuletzt editiert von
          #13

          @tomily
          Noch ein kleiner Nachtrag bzw. erneute Frage :-)

          Ich könnte natürlich im Filesystem der Linuxmaschine, auf der der ioBroker läuft die zu überwachenden Laufwerke mounten.
          Würde diese aber ganz gerne vor dem Prüfen aus dem SCript heraus verbinden und anschließend wieder trennen.

          Kann ich den "Mountbefehl" aus dem JavaScript heraus ausführen?

          E 1 Antwort Letzte Antwort
          0
          • T tomily

            @tomily
            Noch ein kleiner Nachtrag bzw. erneute Frage :-)

            Ich könnte natürlich im Filesystem der Linuxmaschine, auf der der ioBroker läuft die zu überwachenden Laufwerke mounten.
            Würde diese aber ganz gerne vor dem Prüfen aus dem SCript heraus verbinden und anschließend wieder trennen.

            Kann ich den "Mountbefehl" aus dem JavaScript heraus ausführen?

            E Offline
            E Offline
            ente34
            schrieb am zuletzt editiert von
            #14

            @tomily
            Basteln wir mal alles zusammen.

            const fs = require('fs');
            const util = require('util');
            const readdir = util.promisify(fs.readdir);
            const stat = util.promisify(fs.stat);
            const lstat = util.promisify(fs.lstat);
            
            const mountDir = '/home/pi/mount';
            const nasDir = '//nas02/backup'
            const mountCmd = `sudo mount -t cifs -o username=...,password=...,vers=3.0 ${nasDir} ${mountDir}`;
            const umountCmd = `sudo umount ${nasDir}`;
            
            const warnSeconds = 5 * 86400;
            
            async function checkDir(dir) {
                let rc = false;
                try {
                    let files = [];
                    files = await readdir(dir);
            
                    let newestFileAge = 0;
                    let newestFile = '';
            
                    for (let file of files) {
                        let fullpath = dir + '/' + file;
                        const s = await lstat(fullpath);
                        if (s.isFile()) {
                            const stats = await stat(fullpath);
                            if (stats.mtimeMs > newestFileAge) {
                                newestFileAge = stats.mtimeMs;
                                newestFile = file;
                            }
                        }
                    }
            
                    let seconds = (new Date().getTime() - new Date(newestFileAge).getTime()) / 1000;
                    rc = seconds > warnSeconds;
                    console.log(`newest file "${newestFile}" created before ${seconds} seconds`);
                } catch (e) {
                    console.log(e);
                } finally {
                    return rc;
                }
            }
            
            /**
             * Executes a shell command and return it as a Promise.
             * @param cmd {string}
             * @return {Promise<string>}
             */
            function execShellCommand(cmd) {
                const exec = require('child_process').exec;
                return new Promise((resolve, reject) => {
                    exec(cmd, (error, stdout, stderr) => {
                        if (error) {
                            console.warn(error);
                        }
                        resolve(stdout ? stdout : stderr);
                    });
                });
            }
            
            async function checkBackup() {
                try {
                    //hier mount-Kommando
                    await execShellCommand(mountCmd);
            
                    let result = await checkDir(mountDir);
                    if (result) {
                        // Mail abschicken
                        console.log('Send Mail');
                    }
            
                    //hier umount-Kommando
                    await execShellCommand(umountCmd);
                } catch (e) {
                    console.warn(e);
                }
            }
            
            // 22:30
            schedule('30 22 * * *', () => {
                checkBackup();
            });
            
            

            Im Javascript-Adapter "exec" erlauben!

            js4.PNG

            T 1 Antwort Letzte Antwort
            0
            • T tomily

              Hallo zusammen,

              hat jemand eine Idee, wie ich folgendes realisieren kann?

              Ich möchte diverse Unterordner meiner NAS auf neue Dateien überprüfen.
              In den Ordnern liegen unterschiedlich viele Dateien und unterschiedliche Dateitypen.

              Ziel ist es, eine Email auszulösen, wenn die neueste Datei im Verzeichnis älter ist, als X-Tage.

              Damit möchte ich erreichen, dass mir zukünftig auffällt, wenn Datensicherungen nicht mehr erstellt werden.

              Hat jemand eine Idee oder zufällig schon ein fertiges Skript für diesen Monitoring-Zweck?

              Grüße

              mickymM Online
              mickymM Online
              mickym
              Most Active
              schrieb am zuletzt editiert von
              #15

              @tomily Warum nutzt Du nicht die Watch-Node?

              Jeder Flow bzw. jedes Script, das ich hier poste implementiert jeder auf eigene Gefahr. Flows und Scripts können Fehler aufweisen und weder der Seitenbetreiber noch ich persönlich können hierfür haftbar gemacht werden. Das gleiche gilt für Empfehlungen aller Art.

              T 1 Antwort Letzte Antwort
              0
              • E ente34

                @tomily
                Basteln wir mal alles zusammen.

                const fs = require('fs');
                const util = require('util');
                const readdir = util.promisify(fs.readdir);
                const stat = util.promisify(fs.stat);
                const lstat = util.promisify(fs.lstat);
                
                const mountDir = '/home/pi/mount';
                const nasDir = '//nas02/backup'
                const mountCmd = `sudo mount -t cifs -o username=...,password=...,vers=3.0 ${nasDir} ${mountDir}`;
                const umountCmd = `sudo umount ${nasDir}`;
                
                const warnSeconds = 5 * 86400;
                
                async function checkDir(dir) {
                    let rc = false;
                    try {
                        let files = [];
                        files = await readdir(dir);
                
                        let newestFileAge = 0;
                        let newestFile = '';
                
                        for (let file of files) {
                            let fullpath = dir + '/' + file;
                            const s = await lstat(fullpath);
                            if (s.isFile()) {
                                const stats = await stat(fullpath);
                                if (stats.mtimeMs > newestFileAge) {
                                    newestFileAge = stats.mtimeMs;
                                    newestFile = file;
                                }
                            }
                        }
                
                        let seconds = (new Date().getTime() - new Date(newestFileAge).getTime()) / 1000;
                        rc = seconds > warnSeconds;
                        console.log(`newest file "${newestFile}" created before ${seconds} seconds`);
                    } catch (e) {
                        console.log(e);
                    } finally {
                        return rc;
                    }
                }
                
                /**
                 * Executes a shell command and return it as a Promise.
                 * @param cmd {string}
                 * @return {Promise<string>}
                 */
                function execShellCommand(cmd) {
                    const exec = require('child_process').exec;
                    return new Promise((resolve, reject) => {
                        exec(cmd, (error, stdout, stderr) => {
                            if (error) {
                                console.warn(error);
                            }
                            resolve(stdout ? stdout : stderr);
                        });
                    });
                }
                
                async function checkBackup() {
                    try {
                        //hier mount-Kommando
                        await execShellCommand(mountCmd);
                
                        let result = await checkDir(mountDir);
                        if (result) {
                            // Mail abschicken
                            console.log('Send Mail');
                        }
                
                        //hier umount-Kommando
                        await execShellCommand(umountCmd);
                    } catch (e) {
                        console.warn(e);
                    }
                }
                
                // 22:30
                schedule('30 22 * * *', () => {
                    checkBackup();
                });
                
                

                Im Javascript-Adapter "exec" erlauben!

                js4.PNG

                T Offline
                T Offline
                tomily
                schrieb am zuletzt editiert von
                #16

                @ente34
                Geil, das sieht gut aus. Ich teste es gleich heute Abend! DANKE!

                1 Antwort Letzte Antwort
                0
                • mickymM mickym

                  @tomily Warum nutzt Du nicht die Watch-Node?

                  T Offline
                  T Offline
                  tomily
                  schrieb am zuletzt editiert von
                  #17

                  @mickym
                  Hmmmm, weil ich nicht weiß wie ich es damit lösen könnte? :-) Ginge das denn?

                  In diesem Fall habe ich NodeRed gar nicht im Spiel. Wäre aber auch denkbar.

                  mickymM 1 Antwort Letzte Antwort
                  0
                  • T tomily

                    @mickym
                    Hmmmm, weil ich nicht weiß wie ich es damit lösen könnte? :-) Ginge das denn?

                    In diesem Fall habe ich NodeRed gar nicht im Spiel. Wäre aber auch denkbar.

                    mickymM Online
                    mickymM Online
                    mickym
                    Most Active
                    schrieb am zuletzt editiert von mickym
                    #18

                    @tomily Nun ich hätte mich hier nie ;) eingemischt, aber nachdem ich ja weiß, dass Du bislang alles mit NodeRed gemacht hast, war ich etwas verwundert und vielleicht denkst Du ja auch über einen Systemwechsel nach.

                    Außerdem hat hier @ente34 Dir schon viel geholfen und Arbeit reingesteckt und das gebietet schon der Respekt, dass ich mich da nicht einmische.

                    Als ich dann gestern diesen Thread hier entdeckt habe, habe ich mir Deine Anfordeung mal als Herausforderung gestellt und mal einen kleinen Flow gebastelt - wobei ich nicht im Detail weiß, ob das Deinen Bedürfnissen entspricht.

                    Hier mal nur der Flow mit ein paar Erklärungen:

                    02d3eee9-e951-40ab-b35c-ab57d4e015f2-image.png

                    Ich habe der Einfachheit einfach mal angenommen, dass nur ein Backup in ein Verzeichnis schreibt und deshalb nur dieser Task etwas an dem Verzeichnis ändert. Ich habe deshalb mal als Beispiel das iobroker backup genommen.

                    1. Mit der Watch-Node beobachte ich also das "/opt/iobroker/backups" Verzeichnis - welche Änderungen sind mir wurscht - weil ich davon ausgehe, dass das nur der backitup-Adapter ist, der da was verändert.
                    2. Findet eine Aktivität statt läuft z. Bsp der obere Ast los und schreibt einen Eintrag über ein erfolgreiches Backup in die Logdatei .
                    3. Gleichzeitig wird der Trigger im unteren Ast getriggert, der solange nichts verschickt, solange 25 Stunden keine neue Nachricht (also Aktivität) stattfindet.
                    4. Findet keine Aktivität statt schreibt er eine negativen Logeintrag und schickt Dir eine Mail

                    Die Logdatei in der diese Aktivität überwacht wird schaut dann einfach so aus:

                    7875dc65-4da8-4646-9376-d88edf00bac3-image.png

                    und die Mail im Fehlerfall würde dann so aussehen:

                    screen.png

                    und dann kannst Du ja wieder mit Deinem anderen Projekt mehrere Logdateien zu überwachen ggf. fortfahren. ;) ;) ;)

                    [
                       {
                           "id": "9b7a57bd.8ee618",
                           "type": "watch",
                           "z": "6e170384.60c96c",
                           "name": "",
                           "files": "/opt/iobroker/backups",
                           "recursive": "",
                           "x": 2560,
                           "y": 3460,
                           "wires": [
                               [
                                   "94badf85.3ebad",
                                   "eab4a1ca.fda7c"
                               ]
                           ]
                       },
                       {
                           "id": "94badf85.3ebad",
                           "type": "change",
                           "z": "6e170384.60c96c",
                           "name": "true",
                           "rules": [
                               {
                                   "t": "set",
                                   "p": "payload",
                                   "pt": "msg",
                                   "to": "true",
                                   "tot": "bool"
                               }
                           ],
                           "action": "",
                           "property": "",
                           "from": "",
                           "to": "",
                           "reg": false,
                           "x": 2770,
                           "y": 3440,
                           "wires": [
                               [
                                   "5048baed.cf7704"
                               ]
                           ]
                       },
                       {
                           "id": "eab4a1ca.fda7c",
                           "type": "trigger",
                           "z": "6e170384.60c96c",
                           "name": "",
                           "op1": "",
                           "op2": "false",
                           "op1type": "nul",
                           "op2type": "bool",
                           "duration": "25",
                           "extend": true,
                           "overrideDelay": false,
                           "units": "hr",
                           "reset": "",
                           "bytopic": "all",
                           "topic": "topic",
                           "outputs": 1,
                           "x": 2800,
                           "y": 3500,
                           "wires": [
                               [
                                   "db949d4b.5a23d",
                                   "3556f13.927030e"
                               ]
                           ]
                       },
                       {
                           "id": "db949d4b.5a23d",
                           "type": "change",
                           "z": "6e170384.60c96c",
                           "name": "Log-Eintrag",
                           "rules": [
                               {
                                   "t": "change",
                                   "p": "payload",
                                   "pt": "msg",
                                   "from": "true",
                                   "fromt": "bool",
                                   "to": "Backup gestartet ...",
                                   "tot": "str"
                               },
                               {
                                   "t": "change",
                                   "p": "payload",
                                   "pt": "msg",
                                   "from": "false",
                                   "fromt": "bool",
                                   "to": "fehlendes Backup",
                                   "tot": "str"
                               },
                               {
                                   "t": "set",
                                   "p": "payload",
                                   "pt": "msg",
                                   "to": "'iobroker.backup\\t' & $moment($now()).locale(\"de\").tz('Europe/Berlin').format('YYYY-MM-DD HH:mm:ss.SSS') & '\\t' & payload\t",
                                   "tot": "jsonata"
                               }
                           ],
                           "action": "",
                           "property": "",
                           "from": "",
                           "to": "",
                           "reg": false,
                           "x": 3170,
                           "y": 3440,
                           "wires": [
                               [
                                   "14218c4a.ef2f44"
                               ]
                           ]
                       },
                       {
                           "id": "14218c4a.ef2f44",
                           "type": "file",
                           "z": "6e170384.60c96c",
                           "name": "",
                           "filename": "/home/iobroker/iobroker_backups.log",
                           "appendNewline": true,
                           "createDir": false,
                           "overwriteFile": "false",
                           "encoding": "none",
                           "x": 3450,
                           "y": 3440,
                           "wires": [
                               []
                           ]
                       },
                       {
                           "id": "9e3e44a3.7826a8",
                           "type": "e-mail",
                           "z": "6e170384.60c96c",
                           "server": "",
                           "port": "465",
                           "secure": true,
                           "tls": true,
                           "name": "",
                           "dname": "verschicke Mail",
                           "x": 3380,
                           "y": 3500,
                           "wires": []
                       },
                       {
                           "id": "3556f13.927030e",
                           "type": "change",
                           "z": "6e170384.60c96c",
                           "name": "",
                           "rules": [
                               {
                                   "t": "set",
                                   "p": "topic",
                                   "pt": "msg",
                                   "to": "Fehlendes Backup",
                                   "tot": "str"
                               },
                               {
                                   "t": "set",
                                   "p": "payload",
                                   "pt": "msg",
                                   "to": "'Fehlendes Backup festgestellt am: ' & $moment($now()).locale(\"de\").tz('Europe/Berlin').format('YYYY-MM-DD HH:mm:ss.SSS')",
                                   "tot": "jsonata"
                               }
                           ],
                           "action": "",
                           "property": "",
                           "from": "",
                           "to": "",
                           "reg": false,
                           "x": 3020,
                           "y": 3500,
                           "wires": [
                               [
                                   "25921972.cd66c6"
                               ]
                           ]
                       },
                       {
                           "id": "25921972.cd66c6",
                           "type": "template",
                           "z": "6e170384.60c96c",
                           "name": "",
                           "field": "payload",
                           "fieldType": "msg",
                           "format": "handlebars",
                           "syntax": "mustache",
                           "template": "<h1>Warnung: Fehlendes Backup</h1>\n\n{{payload}} !",
                           "output": "str",
                           "x": 3210,
                           "y": 3500,
                           "wires": [
                               [
                                   "9e3e44a3.7826a8"
                               ]
                           ]
                       },
                       {
                           "id": "5048baed.cf7704",
                           "type": "trigger",
                           "z": "6e170384.60c96c",
                           "name": "",
                           "op1": "",
                           "op2": "",
                           "op1type": "pay",
                           "op2type": "nul",
                           "duration": "3",
                           "extend": true,
                           "overrideDelay": false,
                           "units": "min",
                           "reset": "",
                           "bytopic": "all",
                           "topic": "topic",
                           "outputs": 1,
                           "x": 2950,
                           "y": 3440,
                           "wires": [
                               [
                                   "db949d4b.5a23d"
                               ]
                           ]
                       }
                    ]
                    

                    Jeder Flow bzw. jedes Script, das ich hier poste implementiert jeder auf eigene Gefahr. Flows und Scripts können Fehler aufweisen und weder der Seitenbetreiber noch ich persönlich können hierfür haftbar gemacht werden. Das gleiche gilt für Empfehlungen aller Art.

                    1 Antwort Letzte Antwort
                    0
                    • AlCalzoneA Offline
                      AlCalzoneA Offline
                      AlCalzone
                      Developer
                      schrieb am zuletzt editiert von
                      #19

                      Wenn es wirklich nur um neue Dateien (neuer Name, nicht überschrieben) geht, kann man im JS Skript auch lediglich mtime des Verzeichnisses prüfen und muss nicht erst alle Dateien enumerieren und deren Zeitstempel prüfen.

                      Warum `sudo` böse ist: https://forum.iobroker.net/post/17109

                      T M 2 Antworten Letzte Antwort
                      0
                      • AlCalzoneA AlCalzone

                        Wenn es wirklich nur um neue Dateien (neuer Name, nicht überschrieben) geht, kann man im JS Skript auch lediglich mtime des Verzeichnisses prüfen und muss nicht erst alle Dateien enumerieren und deren Zeitstempel prüfen.

                        T Offline
                        T Offline
                        tomily
                        schrieb am zuletzt editiert von
                        #20

                        Hallo zusammen,

                        erst mal einen herzlichen und fetten Dank an @ente34 für den geilen Support.
                        Ich habe das letzte Script noch etwas für mich angepasst und nun tut es EXAKT das was ich mir vorgestellt habe... Juhuuu :-)

                        Danke auch an @mickym. Es ist immer interessant mehrere Wege zu testen. Somit lerne ich beide Systeme. Werde den NodeRed-Flow morgen früh gleich mal ausprobieren. Einfach aus Neugierde.

                        Ich mache viel mit NodeRed. In dem Fall wollte ich das Backup-Prüfscript aber absichtlich in einem anderen System betreiben. Es macht nie Sinn eine Datensicherung vom gleichen System zu prüfen, das es auch erstellt.

                        Spinnt das System, erhält man gar keine Infos mehr. Deshalb arbeite ich mit mehreren Instanzen und Servern.

                        Ich prüfe somit mehrere Verzeichnisse auf neue Sicherungen ab. Ganz egal, wie diese erstellt wurden. Manche kommen vom ioBroker direkt. Manche liefert das ESXi-Serverbackup und manche kopiere ich von Hand rein und werde somit nach einer Gewissen Zeit erinnert es wieder zu tun :-)

                        Das einzige, das nicht funktioniert ist, wenn die Sicherungen in Unterverzeichnissen liegen. Das wäre dann die nächste Hürde :-)

                        mickymM 1 Antwort Letzte Antwort
                        0
                        • T tomily

                          Hallo zusammen,

                          erst mal einen herzlichen und fetten Dank an @ente34 für den geilen Support.
                          Ich habe das letzte Script noch etwas für mich angepasst und nun tut es EXAKT das was ich mir vorgestellt habe... Juhuuu :-)

                          Danke auch an @mickym. Es ist immer interessant mehrere Wege zu testen. Somit lerne ich beide Systeme. Werde den NodeRed-Flow morgen früh gleich mal ausprobieren. Einfach aus Neugierde.

                          Ich mache viel mit NodeRed. In dem Fall wollte ich das Backup-Prüfscript aber absichtlich in einem anderen System betreiben. Es macht nie Sinn eine Datensicherung vom gleichen System zu prüfen, das es auch erstellt.

                          Spinnt das System, erhält man gar keine Infos mehr. Deshalb arbeite ich mit mehreren Instanzen und Servern.

                          Ich prüfe somit mehrere Verzeichnisse auf neue Sicherungen ab. Ganz egal, wie diese erstellt wurden. Manche kommen vom ioBroker direkt. Manche liefert das ESXi-Serverbackup und manche kopiere ich von Hand rein und werde somit nach einer Gewissen Zeit erinnert es wieder zu tun :-)

                          Das einzige, das nicht funktioniert ist, wenn die Sicherungen in Unterverzeichnissen liegen. Das wäre dann die nächste Hürde :-)

                          mickymM Online
                          mickymM Online
                          mickym
                          Most Active
                          schrieb am zuletzt editiert von mickym
                          #21

                          @tomily
                          Ich hab mir nun aber mal das Skript von @ente34 genauer angeschaut und da wird das Verzeichnis ja zum Überprüfungszeitpunkt gemountet, analysiert und dann wieder unmounted.
                          Der flow geht aber einen ganz anderen Ansatz - der permanenten Überprüfung!
                          Deshalb dürfte der Flow für Dich doch nicht geeignet sein. Der Flow geht ja davon aus, dass Du eine permanente Verbindung zur Überwachung hälst (gemountete Verzeinchisse) und nicht nur zu einem bestimmten Zeitpunkt, wie das ja das Script macht. Also insofern vergiss einfach den Flow. ;)

                          Jeder Flow bzw. jedes Script, das ich hier poste implementiert jeder auf eigene Gefahr. Flows und Scripts können Fehler aufweisen und weder der Seitenbetreiber noch ich persönlich können hierfür haftbar gemacht werden. Das gleiche gilt für Empfehlungen aller Art.

                          T 2 Antworten Letzte Antwort
                          0
                          • mickymM mickym

                            @tomily
                            Ich hab mir nun aber mal das Skript von @ente34 genauer angeschaut und da wird das Verzeichnis ja zum Überprüfungszeitpunkt gemountet, analysiert und dann wieder unmounted.
                            Der flow geht aber einen ganz anderen Ansatz - der permanenten Überprüfung!
                            Deshalb dürfte der Flow für Dich doch nicht geeignet sein. Der Flow geht ja davon aus, dass Du eine permanente Verbindung zur Überwachung hälst (gemountete Verzeinchisse) und nicht nur zu einem bestimmten Zeitpunkt, wie das ja das Script macht. Also insofern vergiss einfach den Flow. ;)

                            T Offline
                            T Offline
                            tomily
                            schrieb am zuletzt editiert von
                            #22

                            @mickym
                            Ich danke Dir für den input. Für mich sind trotzdem beide Varianten sehr interessant.

                            Der Ursprüngliche Zweck war ja zu erkennen, wenn keine neue Dateien mehr kommen. Der permanente Überwachung wäre sicherlich auch in einigen Situationen sinnvoll.

                            Zur NOT könnte ich das Verzeichnis dauerhaft mounten. Aber egtl muss und soll das nicht sein.

                            Die Prüfung würde mir 1-2x am Tag reichen. Weiss nicht, ob die „Dauerprüfung“ evtl traffic oder performance Probleme verursacht?

                            @ente34
                            Könnte ich mit dem Skript evtl. Auch zusätlich rekursiv in Unteeverzeichnissen suchen?
                            War zwar keine ursprüngliche Anforderung von mir, aber nachdem ich meine ganzen Check-Jobs eingerichtet habe, bin ich noch darauf gestossen.

                            E 1 Antwort Letzte Antwort
                            0
                            • mickymM mickym

                              @tomily
                              Ich hab mir nun aber mal das Skript von @ente34 genauer angeschaut und da wird das Verzeichnis ja zum Überprüfungszeitpunkt gemountet, analysiert und dann wieder unmounted.
                              Der flow geht aber einen ganz anderen Ansatz - der permanenten Überprüfung!
                              Deshalb dürfte der Flow für Dich doch nicht geeignet sein. Der Flow geht ja davon aus, dass Du eine permanente Verbindung zur Überwachung hälst (gemountete Verzeinchisse) und nicht nur zu einem bestimmten Zeitpunkt, wie das ja das Script macht. Also insofern vergiss einfach den Flow. ;)

                              T Offline
                              T Offline
                              tomily
                              schrieb am zuletzt editiert von
                              #23

                              @mickym hast du den Flow wieder rausgenommen?
                              Darf ich ihn mir trotzdem anschauen? Wäre total super, möchte diese Option nicht ausser Acht lassen.

                              mickymM 1 Antwort Letzte Antwort
                              0
                              • T tomily

                                @mickym hast du den Flow wieder rausgenommen?
                                Darf ich ihn mir trotzdem anschauen? Wäre total super, möchte diese Option nicht ausser Acht lassen.

                                mickymM Online
                                mickymM Online
                                mickym
                                Most Active
                                schrieb am zuletzt editiert von
                                #24

                                @tomily Hängt dran.

                                Jeder Flow bzw. jedes Script, das ich hier poste implementiert jeder auf eigene Gefahr. Flows und Scripts können Fehler aufweisen und weder der Seitenbetreiber noch ich persönlich können hierfür haftbar gemacht werden. Das gleiche gilt für Empfehlungen aller Art.

                                1 Antwort Letzte Antwort
                                0
                                • T tomily

                                  @mickym
                                  Ich danke Dir für den input. Für mich sind trotzdem beide Varianten sehr interessant.

                                  Der Ursprüngliche Zweck war ja zu erkennen, wenn keine neue Dateien mehr kommen. Der permanente Überwachung wäre sicherlich auch in einigen Situationen sinnvoll.

                                  Zur NOT könnte ich das Verzeichnis dauerhaft mounten. Aber egtl muss und soll das nicht sein.

                                  Die Prüfung würde mir 1-2x am Tag reichen. Weiss nicht, ob die „Dauerprüfung“ evtl traffic oder performance Probleme verursacht?

                                  @ente34
                                  Könnte ich mit dem Skript evtl. Auch zusätlich rekursiv in Unteeverzeichnissen suchen?
                                  War zwar keine ursprüngliche Anforderung von mir, aber nachdem ich meine ganzen Check-Jobs eingerichtet habe, bin ich noch darauf gestossen.

                                  E Offline
                                  E Offline
                                  ente34
                                  schrieb am zuletzt editiert von
                                  #25

                                  @tomily sagte in Ordner auf neue Dateien überwachen:

                                  Könnte ich mit dem Skript evtl. Auch zusätlich rekursiv in Unteeverzeichnissen suchen?

                                  Man muss das Script nur ein wenig umstellen:

                                  const fs = require('fs');
                                  const util = require('util');
                                  const readdir = util.promisify(fs.readdir);
                                  const stat = util.promisify(fs.stat);
                                  const lstat = util.promisify(fs.lstat);
                                  
                                  const mountDir = '/home/pi/mount';
                                  const nasDir = '//nas02/backup/raspi-iobroker';
                                  const mountCmd = `sudo mount -t cifs -o username=...,password=...,vers=3.0 ${nasDir} ${mountDir}`;
                                  const umountCmd = `sudo umount ${nasDir}`;
                                  
                                  const warnSeconds = 5 * 86400;
                                  
                                  async function NewestFile(dir) {
                                      let newestFileAge = 0;
                                      let newestFile = '';
                                      //console.log('### '+dir);
                                      try {
                                          let files = [];
                                          files = await readdir(dir);
                                  
                                  
                                          for (let file of files) {
                                              let fullpath = dir + '/' + file;
                                              const s = await lstat(fullpath);
                                              if (s.isFile()) {
                                                  const stats = await stat(fullpath);
                                                  if (stats.mtimeMs > newestFileAge) {
                                                      newestFileAge = stats.mtimeMs;
                                                      newestFile = file;
                                                  }
                                              } else {
                                                  let values = await NewestFile(fullpath);
                                                  if (values.age > newestFileAge) {
                                                      newestFileAge = values.age;
                                                      newestFile = values.file;
                                                  }
                                              }
                                          }
                                      } catch (e) {
                                          console.log(e);
                                      } finally {
                                          return { file: newestFile,  age:newestFileAge };
                                      }
                                  }
                                  
                                  /**
                                   * Executes a shell command and return it as a Promise.
                                   * @param cmd {string}
                                   * @return {Promise<string>}
                                   */
                                  function execShellCommand(cmd) {
                                      const exec = require('child_process').exec;
                                      return new Promise((resolve, reject) => {
                                          exec(cmd, (error, stdout, stderr) => {
                                              if (error) {
                                                  console.warn(error);
                                              }
                                              resolve(stdout ? stdout : stderr);
                                          });
                                      });
                                  }
                                  
                                  async function checkBackup() {
                                      try {
                                          //hier mount-Kommando
                                          await execShellCommand(mountCmd);
                                  
                                          let result = await NewestFile(mountDir);
                                          let seconds = (new Date().getTime() - new Date(result.age).getTime()) / 1000;
                                          if (seconds > warnSeconds) {
                                              console.log(`newest file "${result.file}" created before ${seconds} seconds`);
                                              // Mail abschicken
                                              console.log('Send Mail');
                                          }
                                  
                                          //hier umount-Kommando
                                          await execShellCommand(umountCmd);
                                      } catch (e) {
                                          console.warn(e);
                                      }
                                  }
                                  
                                  // 22:30
                                  schedule('30 22 * * *', () => {
                                      checkBackup();
                                  });
                                  T 1 Antwort Letzte Antwort
                                  0
                                  • E ente34

                                    @tomily sagte in Ordner auf neue Dateien überwachen:

                                    Könnte ich mit dem Skript evtl. Auch zusätlich rekursiv in Unteeverzeichnissen suchen?

                                    Man muss das Script nur ein wenig umstellen:

                                    const fs = require('fs');
                                    const util = require('util');
                                    const readdir = util.promisify(fs.readdir);
                                    const stat = util.promisify(fs.stat);
                                    const lstat = util.promisify(fs.lstat);
                                    
                                    const mountDir = '/home/pi/mount';
                                    const nasDir = '//nas02/backup/raspi-iobroker';
                                    const mountCmd = `sudo mount -t cifs -o username=...,password=...,vers=3.0 ${nasDir} ${mountDir}`;
                                    const umountCmd = `sudo umount ${nasDir}`;
                                    
                                    const warnSeconds = 5 * 86400;
                                    
                                    async function NewestFile(dir) {
                                        let newestFileAge = 0;
                                        let newestFile = '';
                                        //console.log('### '+dir);
                                        try {
                                            let files = [];
                                            files = await readdir(dir);
                                    
                                    
                                            for (let file of files) {
                                                let fullpath = dir + '/' + file;
                                                const s = await lstat(fullpath);
                                                if (s.isFile()) {
                                                    const stats = await stat(fullpath);
                                                    if (stats.mtimeMs > newestFileAge) {
                                                        newestFileAge = stats.mtimeMs;
                                                        newestFile = file;
                                                    }
                                                } else {
                                                    let values = await NewestFile(fullpath);
                                                    if (values.age > newestFileAge) {
                                                        newestFileAge = values.age;
                                                        newestFile = values.file;
                                                    }
                                                }
                                            }
                                        } catch (e) {
                                            console.log(e);
                                        } finally {
                                            return { file: newestFile,  age:newestFileAge };
                                        }
                                    }
                                    
                                    /**
                                     * Executes a shell command and return it as a Promise.
                                     * @param cmd {string}
                                     * @return {Promise<string>}
                                     */
                                    function execShellCommand(cmd) {
                                        const exec = require('child_process').exec;
                                        return new Promise((resolve, reject) => {
                                            exec(cmd, (error, stdout, stderr) => {
                                                if (error) {
                                                    console.warn(error);
                                                }
                                                resolve(stdout ? stdout : stderr);
                                            });
                                        });
                                    }
                                    
                                    async function checkBackup() {
                                        try {
                                            //hier mount-Kommando
                                            await execShellCommand(mountCmd);
                                    
                                            let result = await NewestFile(mountDir);
                                            let seconds = (new Date().getTime() - new Date(result.age).getTime()) / 1000;
                                            if (seconds > warnSeconds) {
                                                console.log(`newest file "${result.file}" created before ${seconds} seconds`);
                                                // Mail abschicken
                                                console.log('Send Mail');
                                            }
                                    
                                            //hier umount-Kommando
                                            await execShellCommand(umountCmd);
                                        } catch (e) {
                                            console.warn(e);
                                        }
                                    }
                                    
                                    // 22:30
                                    schedule('30 22 * * *', () => {
                                        checkBackup();
                                    });
                                    T Offline
                                    T Offline
                                    tomily
                                    schrieb am zuletzt editiert von
                                    #26

                                    @ente34
                                    Bitte entschuldigt die erneute späte Antwort. Ich bin wirklich schlimm! :)

                                    Habe mir das erste Skript schon etwas auf meine Bedürfnisse angepasst. Das gleiche musste ich mit dem zweiten nun auch tun und es FUNKTIONIERT TADELLOS :-)

                                    Ich habe mir nun für mehrere Ordner und Dateien unterschiedliche Skripte zusammengebaut, die ich hinterinander laufen lasse.

                                    Das passt für mich nun hervorragend.

                                    An der Stelle möchte ich mich recht herzlich für eure konstruktive Vorschläge bedanken. Auch Dir @ente34 herzlichen Dank für die Mühe und Zeit.

                                    Bin sehr happy über das Ergebnis :)

                                    T 1 Antwort Letzte Antwort
                                    0
                                    • T tomily

                                      @ente34
                                      Bitte entschuldigt die erneute späte Antwort. Ich bin wirklich schlimm! :)

                                      Habe mir das erste Skript schon etwas auf meine Bedürfnisse angepasst. Das gleiche musste ich mit dem zweiten nun auch tun und es FUNKTIONIERT TADELLOS :-)

                                      Ich habe mir nun für mehrere Ordner und Dateien unterschiedliche Skripte zusammengebaut, die ich hinterinander laufen lasse.

                                      Das passt für mich nun hervorragend.

                                      An der Stelle möchte ich mich recht herzlich für eure konstruktive Vorschläge bedanken. Auch Dir @ente34 herzlichen Dank für die Mühe und Zeit.

                                      Bin sehr happy über das Ergebnis :)

                                      T Offline
                                      T Offline
                                      tomily
                                      schrieb am zuletzt editiert von
                                      #27

                                      @tomily
                                      Hey zusammen,

                                      habe die Skripte nun einige Zeit am laufen. Funktioniert super und hab mich schon mehrmals auf nicht mehr funktionierende Backups hingewiesen :-)

                                      ZIEL ERREICHT =)

                                      Eine Frage bzw. weitere Idee habe ich noch:

                                      Kann ich das Skript auch "umdrehen", sodass auf NEUE DATEIEN geprüft wird?
                                      Also: Verzeichniss sollte immer leer sein und sobald eine oder mehrer Dateien drin liegen, wird ausgelöst?

                                      Muss ich nur die Abfrage nach Zeit umdrehen?

                                      Beste Grüße
                                      Tom

                                      E 1 Antwort Letzte Antwort
                                      0
                                      • T tomily

                                        @tomily
                                        Hey zusammen,

                                        habe die Skripte nun einige Zeit am laufen. Funktioniert super und hab mich schon mehrmals auf nicht mehr funktionierende Backups hingewiesen :-)

                                        ZIEL ERREICHT =)

                                        Eine Frage bzw. weitere Idee habe ich noch:

                                        Kann ich das Skript auch "umdrehen", sodass auf NEUE DATEIEN geprüft wird?
                                        Also: Verzeichniss sollte immer leer sein und sobald eine oder mehrer Dateien drin liegen, wird ausgelöst?

                                        Muss ich nur die Abfrage nach Zeit umdrehen?

                                        Beste Grüße
                                        Tom

                                        E Offline
                                        E Offline
                                        ente34
                                        schrieb am zuletzt editiert von
                                        #28

                                        @tomily
                                        Das geht leider nicht. Ich habe das Script mal erweitert,
                                        Du musst chkDirNotEmpty() oder chkDirTreeNotEmpty() benutzen, je nachdem, ob Du ein einzelnes Directory oder rekursiv den ganzen Baum absuchen willst

                                        const fs = require('fs');
                                        const util = require('util');
                                        const readdir = util.promisify(fs.readdir);
                                        const stat = util.promisify(fs.stat);
                                        const lstat = util.promisify(fs.lstat);
                                        
                                        const mountDir = '/home/pi/mount';
                                        const nasDir = '//nas02/backup/raspi-iobroker/backitup';
                                        const mountCmd = `sudo mount -t cifs -o username=rsync,password=...,vers=3.0 ${nasDir} ${mountDir}`;
                                        const umountCmd = `sudo umount ${nasDir}`;
                                        
                                        const warnSeconds = 5 * 86400;
                                        
                                        async function NewestFile(dir) {
                                            let newestFileAge = 0;
                                            let newestFile = '';
                                            //console.log('### '+dir);
                                            try {
                                                let files = [];
                                                files = await readdir(dir);
                                        
                                        
                                                for (let file of files) {
                                                    let fullpath = dir + '/' + file;
                                                    const s = await lstat(fullpath);
                                                    if (s.isFile()) {
                                                        const stats = await stat(fullpath);
                                                        if (stats.mtimeMs > newestFileAge) {
                                                            newestFileAge = stats.mtimeMs;
                                                            newestFile = file;
                                                        }
                                                    } else {
                                                        let values = await NewestFile(fullpath);
                                                        if (values.age > newestFileAge) {
                                                            newestFileAge = values.age;
                                                            newestFile = values.file;
                                                        }
                                                    }
                                                }
                                        
                                            } catch (e) {
                                                console.log(e);
                                            } finally {
                                                return { file: newestFile,  age:newestFileAge };
                                            }
                                        }
                                        
                                        async function chkDirNotEmpty(dir) {
                                            let files = await readdir(dir); 
                                            if ( files.length ){
                                                return files[0];
                                            }
                                            return "";
                                        }
                                        
                                        async function chkDirTreeNotEmpty(dir) {
                                            let foundFile = '';
                                            try {
                                                let files = [];
                                                files = await readdir(dir);
                                        
                                                for (let file of files) {
                                                    let fullpath = dir + '/' + file;
                                                    const s = await lstat(fullpath);
                                                    if (s.isFile()) {
                                                        foundFile = fullpath;
                                                        break;
                                                    } else {
                                                        let rc = await chkDirTreeNotEmpty(fullpath);
                                                        if (rc != "") {
                                                            foundFile = rc;
                                                            break;
                                                        }
                                                    }
                                                }
                                        
                                            } catch (e) {
                                                console.log(e);
                                            } finally {
                                                return foundFile;
                                            }
                                        }
                                        
                                        /**
                                         * Executes a shell command and return it as a Promise.
                                         * @param cmd {string}
                                         * @return {Promise<string>}
                                         */
                                        function execShellCommand(cmd) {
                                            const exec = require('child_process').exec;
                                            return new Promise((resolve, reject) => {
                                                exec(cmd, (error, stdout, stderr) => {
                                                    if (error) {
                                                        console.warn(error);
                                                    }
                                                    resolve(stdout ? stdout : stderr);
                                                });
                                            });
                                        }
                                        
                                        async function checkBackup() {
                                            try {
                                                //hier mount-Kommando
                                                await execShellCommand(mountCmd);
                                                
                                                //Nach leeren Directory suchen (nicht rekursiv, gefunden werden Files oder Directories)
                                                let file = await chkDirNotEmpty(mountDir);
                                                
                                                //Nach Files im Directory-Tree suchen (rekursiv)
                                                //file = await chkDirTreeNotEmpty(mountDir);
                                                if (file != "") {
                                                    console.log(`directory tree ${mountDir} not empty, found file "${file}" `);
                                                    // Mail abschicken
                                                    console.log('Send Mail');
                                        
                                                }
                                               
                                                let result = await NewestFile(mountDir);
                                                let seconds = (new Date().getTime() - new Date(result.age).getTime()) / 1000;
                                                if (seconds > warnSeconds) {
                                                    console.log(`newest file "${result.file}" created before ${seconds} seconds`);
                                                    // Mail abschicken
                                                    console.log('Send Mail');
                                                }
                                        
                                                //hier umount-Kommando
                                                await execShellCommand(umountCmd);
                                            } catch (e) {
                                                console.warn(e);
                                            }
                                        }
                                        
                                        // 22:30
                                        schedule('30 22 * * *', () => {
                                            checkBackup();
                                        });
                                        checkBackup();
                                        
                                        
                                        T 1 Antwort Letzte Antwort
                                        0
                                        • T tomily

                                          Hallo zusammen,

                                          hat jemand eine Idee, wie ich folgendes realisieren kann?

                                          Ich möchte diverse Unterordner meiner NAS auf neue Dateien überprüfen.
                                          In den Ordnern liegen unterschiedlich viele Dateien und unterschiedliche Dateitypen.

                                          Ziel ist es, eine Email auszulösen, wenn die neueste Datei im Verzeichnis älter ist, als X-Tage.

                                          Damit möchte ich erreichen, dass mir zukünftig auffällt, wenn Datensicherungen nicht mehr erstellt werden.

                                          Hat jemand eine Idee oder zufällig schon ein fertiges Skript für diesen Monitoring-Zweck?

                                          Grüße

                                          OliverIOO Offline
                                          OliverIOO Offline
                                          OliverIO
                                          schrieb am zuletzt editiert von
                                          #29

                                          @tomily sagte in Ordner auf neue Dateien überwachen:

                                          Hallo zusammen,

                                          hat jemand eine Idee, wie ich folgendes realisieren kann?

                                          Ich möchte diverse Unterordner meiner NAS auf neue Dateien überprüfen.
                                          In den Ordnern liegen unterschiedlich viele Dateien und unterschiedliche Dateitypen.

                                          Ziel ist es, eine Email auszulösen, wenn die neueste Datei im Verzeichnis älter ist, als X-Tage.

                                          Damit möchte ich erreichen, dass mir zukünftig auffällt, wenn Datensicherungen nicht mehr erstellt werden.

                                          Hat jemand eine Idee oder zufällig schon ein fertiges Skript für diesen Monitoring-Zweck?

                                          Grüße

                                          Warum nimmst du nicht eine Standard Bibliothek von Node?
                                          Diese hat Cross Platform Support und funktioniert also für win linux und macos
                                          35 mio downloads pro Woche können nicht irren
                                          https://www.npmjs.com/package/chokidar

                                          Meine Adapter und Widgets
                                          TVProgram, SqueezeboxRPC, OpenLiga, RSSFeed, MyTime,, pi-hole2, vis-json-template, skiinfo, vis-mapwidgets, vis-2-widgets-rssfeed
                                          Links im Profil

                                          1 Antwort Letzte Antwort
                                          0
                                          Antworten
                                          • In einem neuen Thema antworten
                                          Anmelden zum Antworten
                                          • Älteste zuerst
                                          • Neuste zuerst
                                          • Meiste Stimmen


                                          Support us

                                          ioBroker
                                          Community Adapters
                                          Donate

                                          810

                                          Online

                                          32.4k

                                          Benutzer

                                          81.5k

                                          Themen

                                          1.3m

                                          Beiträge
                                          Community
                                          Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
                                          ioBroker Community 2014-2025
                                          logo
                                          • Anmelden

                                          • Du hast noch kein Konto? Registrieren

                                          • Anmelden oder registrieren, um zu suchen
                                          • Erster Beitrag
                                            Letzter Beitrag
                                          0
                                          • Home
                                          • Aktuell
                                          • Tags
                                          • Ungelesen 0
                                          • Kategorien
                                          • Unreplied
                                          • Beliebt
                                          • GitHub
                                          • Docu
                                          • Hilfe