Web-Dateimanager Sprut.IO in OpenSource

    In Beget waren wir erfolgreich und erfolgreich im Bereich virtuelles Hosting tätig, verwenden viele OpenSource-Lösungen und jetzt ist es an der Zeit, unsere Entwicklung mit der Community zu teilen: den Sprut.IO- Dateimanager , den wir für unsere Benutzer entwickelt haben und der in unserem Control Panel verwendet wird. Wir laden alle ein, sich seiner Entwicklung anzuschließen. Wir werden in diesem Artikel darüber berichten, wie es entwickelt wurde und warum wir mit den vorhandenen Analoga nicht zufrieden waren, welche technischen Krücken wir verwendet haben und für wen es nützlich sein könnte.

    Projektseite: https://sprut.io
    Demo ist verfügbar unter: https://demo.sprut.io:9443
    Quellcode: https://github.com/LTD-Beget/sprutio



    Warum den Dateimanager neu erfinden?


    Im Jahr 2010 verwendeten wir NetFTP, was die Probleme beim Öffnen / Laden / Korrigieren mehrerer Dateien ziemlich erträglich löste.
    Manchmal wollten Benutzer jedoch wissen, wie sie Websites zwischen Hosts oder zwischen unseren Konten übertragen können, aber die Website war groß und das Internet war nicht die beste für Benutzer. Am Ende haben wir es entweder selbst gemacht (was offensichtlich schneller war) oder erklärt, was SSH, MC, SCP und andere schreckliche Dinge sind.

    Dann kamen wir auf die Idee, WEB zu einem Zwei-Panel-Dateimanager zu machen, der auf der Serverseite arbeitet und mit der Servergeschwindigkeit zwischen verschiedenen Quellen kopieren kann und in dem es auch Folgendes geben wird: Suche nach Dateien und Verzeichnissen, Analyse des belegten Speicherplatzes (analog zu ncdu), einfache Dateiuploads, naja und viele interessante Dinge. Im Allgemeinen alles, was unseren Nutzern und uns das Leben leichter machen würde.

    Im Mai 2013 haben wir es in der Produktion auf unserem Hosting veröffentlicht. In einigen Momenten stellte sich heraus, dass es sogar noch besser war, als wir es ursprünglich wollten - um Dateien herunterzuladen und auf das lokale Dateisystem zuzugreifen, haben wir ein Java-Applet geschrieben, mit dem Sie Dateien auswählen und alles auf einmal auf das Hosting kopieren können oder umgekehrt vom Hosting (wo das Kopieren nicht so wichtig ist, wusste er, wie man damit arbeitet) Remote-FTP und mit dem Home-Verzeichnis des Benutzers, wird jedoch von Browsern in Kürze leider nicht unterstützt.

    Nachdem wir über das Analogon auf Habré gelesen hatten , entschieden wir uns, unser Produkt in OpenSource zu integrieren, was sich für uns als großartig und nützlich herausstellte . Es dauerte weitere neun Monate, um es von unserer Infrastruktur zu trennen und zu einem angemessenen Erscheinungsbild zu bringen. Vor dem neuen Jahr 2016 haben wir Sprut.IO veröffentlicht.

    Wie arbeitet er?


    Wir haben für uns selbst gemacht und unserer Meinung nach die meisten neuen, stilvollen Jugendwerkzeuge und -technologien verwendet. Oft verwendet, was schon für etwas getan wurde.
    Aufgrund der Interaktion mit unserem Panel gibt es einige Unterschiede in der Implementierung von Sprut.IO und der Version für unser Hosting. Für uns selbst verwenden wir: Vollwertige Warteschlangen, MySQL, einen zusätzlichen Berechtigungsserver, der auch für die Auswahl des Zielservers verantwortlich ist, auf dem sich der Client befindet, den Transport zwischen unseren Servern im internen Netzwerk usw.

    Sprut.IO besteht aus mehreren logischen Komponenten:
    1) Web- Muzzle ,
    2) Nginx + Tornado, Annahme aller Anrufe aus dem Web,
    3) End-Agents, die auf einem oder mehreren Servern platziert werden können.

    In der Tat können Sie durch Hinzufügen einer separaten Ebene mit Autorisierung und Serverauswahl einen Multi-Server-Dateimanager erstellen (wie in unserer Implementierung). Alle Elemente können logisch in zwei Teile unterteilt werden: Frontend (ExtJS, Nginx, Tornado) und Backend (MessagePack Server, SQLite, Redis).

    Das Interaktionsschema ist unten dargestellt:



    Frontend


    Webinterface - alles ist ganz einfach, ExtJS und viel Code. Der Code wurde in CoffeeScript geschrieben. In den ersten Versionen verwendeten sie LocalStorage zum Cachen, lehnten dies jedoch ab, da die Anzahl der Fehler den Vorteil überstieg. Nginx wird zum Rendern von Statik, JS-Code und Dateien über X-Accel-Redirect verwendet (siehe unten). Er überträgt den Rest einfach an Tornado, der wiederum eine Art Router ist und Anfragen an das gewünschte Backend umleitet. Tornado skaliert gut und hoffentlich schneiden wir alle Schlösser aus, die wir selbst gemacht haben.

    Backend


    Das Backend besteht aus mehreren Dämonen, die wie gewohnt Anfragen vom Frontend entgegennehmen können. Daemons befinden sich auf jedem Zielserver und arbeiten mit dem lokalen Dateisystem, laden Dateien über FTP hoch, führen Authentifizierung und Autorisierung durch und arbeiten mit SQLite (Editoreinstellungen, Zugriff auf die FTP-Server des Benutzers).

    Es gibt zwei Arten von Anforderungen, die an das Backend gesendet werden: synchron, die relativ schnell ausgeführt werden (z. B. Auflisten von Dateien, Lesen einer Datei) und Anforderungen zum Ausführen langer Aufgaben (Hochladen einer Datei auf einen Remote-Server, Löschen von Dateien / Verzeichnissen usw.).

    Synchrone Anforderungen sind reguläre RPC-Anforderungen. Als Methode zur Serialisierung von Daten wird msgpack verwendet, das sich unter anderem in Bezug auf die Geschwindigkeit der Serialisierung / Deserialisierung von Daten und deren Unterstützung bewährt hat. Wir haben auch das pythonspezifische rfoo und den Google-Protobuf in Betracht gezogen, aber der erste passte aufgrund der Bindung an Python (und seine Versionen) nicht, und Protobuf mit seinen Codegeneratoren schien für uns überflüssig, weil Die Anzahl der Remote-Prozeduren wird nicht in Dutzenden und Hunderten gemessen, und es war nicht erforderlich, die API in separate Protodateien zu übertragen.

    Wir haben uns entschlossen, Anforderungen für die Ausführung langer Operationen so einfach wie möglich zu gestalten: Zwischen Frontend und Backend befindet sich ein gemeinsames Redis, das die ausgeführte Aufgabe, ihren Status und alle anderen Daten speichert. Zum Starten einer Aufgabe wird eine reguläre synchrone RPC-Anforderung verwendet. Der Flow sieht dann so aus:



    1. Frontend setzt eine Warteaufgabe in einen Rettich
    2. Das Frontend stellt im Backend eine synchrone Anfrage und übergibt dort die Task-ID
    3. Das Backend akzeptiert die Aufgabe, setzt den Status auf "running", erstellt eine Verzweigung und führt die Aufgabe im untergeordneten Prozess aus und gibt sofort eine Antwort an das Backend zurück
    4. Das Frontend überprüft den Status einer Aufgabe oder verfolgt Änderungen an Daten (z. B. die Anzahl der kopierten Dateien, die regelmäßig vom Backend aktualisiert werden).


    Interessante Fälle, die es zu erwähnen gilt


    • Laden Sie Dateien vom Frontend herunter


      Aufgabe:
      Laden Sie die Datei auf den Zielserver hoch, während Frontend keinen Zugriff auf das Dateisystem des Zielservers hat.

      Lösung:
      msgpack-server war nicht zum Übertragen von Dateien geeignet, der Hauptgrund war, dass das Paket nicht byteweise übertragen werden konnte, sondern nur in seiner Gesamtheit (es muss zuerst vollständig in den Speicher geladen und erst dann serialisiert und übertragen werden, bei einer großen Dateigröße wird es OOM geben ) wurde letztendlich entschieden, einen separaten Daemon zu verwenden.
      Der Vorgang stellte sich wie folgt heraus:
      Wir holen die Datei von nginx, schreiben sie in den Socket unseres Daemons mit einem Header, in dem der temporäre Speicherort der Datei angegeben ist. Nachdem die Datei vollständig übertragen wurde, senden wir eine Anfrage an RPC, um die Datei an den endgültigen Speicherort zu verschieben (bereits an den Benutzer). Um mit dem Sockel zu arbeiten, verwenden wir das Paketpysendfile , der Server selbst ist auf der Basis der Standard-Python-Bibliothek asyncore selbst geschrieben

    • Codierungsdefinition


      Aufgabe:
      Öffnen Sie die zu bearbeitende Datei mit der Definition der Kodierung und schreiben Sie unter Berücksichtigung der ursprünglichen Kodierung.

      Probleme:
      Wenn der Benutzer die Codierung nicht richtig erkannt hat, können wir beim Vornehmen von Änderungen an der Datei mit anschließender Aufzeichnung einen UnicodeDecodeError erhalten und die Änderungen werden nicht aufgezeichnet.

      Alle "Krücken", die letztendlich gemacht wurden, sind das Ergebnis der Arbeit an Tickets mit Dateien, die von Benutzern erhalten wurden. Wir verwenden auch alle "Problem" -Dateien zum Testen, nachdem wir Änderungen am Code vorgenommen haben.

      Lösung:
      Nachdem wir im Internet nach dieser Lösung gesucht hatten, fanden wir die Chardet- Bibliothek . Diese Bibliothek ist wiederum ein Port der Uchardet- Bibliothekvon Mozilla. Beispielsweise wird es im bekannten Editor https://notepad-plus-plus.org verwendet.

      Nachdem wir es anhand von Beispielen getestet hatten, stellten wir fest, dass es in Wirklichkeit falsch sein könnte. Anstelle von CP-1251 kann beispielsweise "MacCyrillic" oder "ISO-8859-7" ausgegeben werden, und anstelle von UTF-8 kann "ISO-8859-2" oder ein Sonderfall von "ASCII" angegeben werden.

      Außerdem waren einige der Dateien auf dem Hosting utf-8, aber sie enthielten seltsame Zeichen, entweder von Editoren, die mit UTF nicht richtig arbeiten konnten, oder von denen aus, besonders in solchen Fällen, Krücken hinzugefügt werden mussten.

      Ein Beispiel für die Codierungserkennung und das Lesen von Dateien mit Kommentaren
      # Для определения кодировки используем порт uchardet от Mozilla - python chardet 
      # https://github.com/chardet/chardet
      #
      # Используем dev версию, там все самое свежее.
      # Данный код постоянно улучшается благодаря обратной связи с пользователями
      # чем больше - тем точнее определяется кодировка, но медленнее. 50000 - выбрано опытным путем
      self.charset_detect_buffer = 50000
      # Берем часть файла
      part_content = content[0:self.charset_detect_buffer] + content[-self.charset_detect_buffer:]
      chardet_result = chardet.detect(part_content)
      detected = chardet_result["encoding"]
      confidence = chardet_result["confidence"]
      # костыль для тех, кто использует кривые редакторы в windows
      # из-за этого в файлах utf-8 имеем cp-1251 из-за чего библиотека ведет себя непредсказуемо при детектировании
      re_utf8 = re.compile('.*charset\s*=\s*utf\-8.*', re.UNICODE | re.IGNORECASE | re.MULTILINE)
      html_ext = ['htm', 'html', 'phtml', 'php', 'inc', 'tpl', 'xml']
      # Все вероятности выбраны опытным путем, на основе набора файлов для тестирования
      if confidence > 0.75 and detected != 'windows-1251' and detected != FM.DEFAULT_ENCODING:
          if detected == "ISO-8859-7":
              detected = "windows-1251"
          if detected == "ISO-8859-2":
              detected = "utf-8"
          if detected == "ascii":
              detected = "utf-8"
          if detected == "MacCyrillic":
              detected = "windows-1251"
          # если все же ошиблись - костыль на указанный в файле charset
          if detected != FM.DEFAULT_ENCODING and file_ext in html_ext:
              result_of_search = re_utf8.search(part_content)
              self.logger.debug(result_of_search)
              if result_of_search is not None:
                  self.logger.debug("matched utf-8 charset")
                  detected = FM.DEFAULT_ENCODING
              else:
                  self.logger.debug("not matched utf-8 charset")
      elif confidence > 0.60 and detected != 'windows-1251' and detected != FM.DEFAULT_ENCODING:
          # Тут отдельная логика
          # Код убран для краткости из примера
      elif detected == 'windows-1251' or detected == FM.DEFAULT_ENCODING:
          pass
      # Если определилось не очень верно, то тогда, скорее всего, это ошибка и берем UTF-8 ))
      else:
          detected = FM.DEFAULT_ENCODING
      encoding = detected if (detected or "").lower() in FM.encodings else FM.DEFAULT_ENCODING
      answer = {
          "item": self._make_file_info(abs_path),
          "content": content,
          "encoding": encoding
      }


    • Parallele Suche nach Text in Dateien basierend auf der Dateicodierung


      Aufgabe:
      Organisieren der Textsuche in Dateien mit der Möglichkeit, den Namen "Platzhalter im Shell-Stil" zu verwenden, z. B. "pupkin@*.com" "$ * = 42;" usw.

      Probleme: Der
      Benutzer gibt das Wort "Kontakte" ein - die Suche zeigt, dass es keine Dateien mit dem angegebenen Text gibt, aber in Wirklichkeit sind es diese, aber auf dem Hosting stoßen wir auf viele Kodierungen, sogar im Rahmen eines Projekts. Daher sollte die Suche dies auch berücksichtigen.

      Mehrmals stießen wir auf die Tatsache, dass Benutzer versehentlich Zeilen eingeben und mehrere Suchvorgänge für eine große Anzahl von Ordnern ausführen konnten. Dies führte in Zukunft zu einer Erhöhung der Auslastung der Server.

      Lösung:
      Multitasking wurde mit dem Multiprocessing- Modul ganz normal organisiertund zwei Warteschlangen (eine Liste aller Dateien, eine Liste der gefundenen Dateien mit den erforderlichen Einträgen). Ein Mitarbeiter erstellt eine Liste von Dateien, und der Rest arbeitet parallel, analysiert sie und führt eine direkte Suche durch.

      Die gewünschte Zeichenfolge kann mit dem Paket fnmatch als regulärer Ausdruck dargestellt werden . Link zur endgültigen Suchimplementierung.

      Um das Problem mit Codierungen zu lösen, wird ein Codebeispiel mit Kommentaren angegeben, in dem das bekannte chardet- Paket verwendet wird .

      Beispiel für die Implementierung eines Workers
      # Приведен пример воркера
      self.re_text = re.compile('.*' + fnmatch.translate(self.text)[:-7] + '.*', re.UNICODE | re.IGNORECASE)
      # remove \Z(?ms) from end of result expression
      def worker(re_text, file_queue, result_queue, logger, timeout):
          while int(time.time()) < timeout:
              if file_queue.empty():
                continue
              f_path = file_queue.get()
              try:
                  if is_binary(f_path):
                      continue
                  mime = mimetypes.guess_type(f_path)[0]
                  # исключаем некоторые mime типы из поиска
                  if mime in ['application/pdf', 'application/rar']:
                      continue
                  with open(f_path, 'rb') as fp:
                      for line in fp:
                          try:
                              # преобразуем в unicode
                              line = as_unicode(line)
                          except UnicodeDecodeError:
                              # видимо, файл не unicode, определим кодировку
                              charset = chardet.detect(line)
                              # бывает не всегда правильно
                              if charset.get('encoding') in ['MacCyrillic']:
                                  detected = 'windows-1251'
                              else:
                                  detected = charset.get('encoding')
                              if detected is None:
                                  # не получилось (
                                  break
                              try:
                                  # будем искать в нужной кодировке
                                  line = str(line, detected, "replace")
                              except LookupError:
                                  pass
                          if re_text.match(line) is not None:
                              result_queue.put(f_path)
                              logger.debug("matched file = %s " % f_path)
                              break
              except UnicodeDecodeError as unicode_e:
                  logger.error(
                      "UnicodeDecodeError %s, %s" % (str(unicode_e), traceback.format_exc()))
              except IOError as io_e:
                  logger.error("IOError %s, %s" % (str(io_e), traceback.format_exc()))

      In der letzten Implementierung wurde die Möglichkeit hinzugefügt, die Ausführungszeit in Sekunden (Timeout) festzulegen. Die Standardeinstellung ist 1 Stunde. In den Prozessen der Arbeiter selbst wird die Ausführungspriorität verringert, um die Belastung der Platte und des Prozessors zu verringern.

    • Entpacken und Erstellen von Dateiarchiven


      Aufgabe:
      Benutzern die Möglichkeit geben, Archive zu erstellen (zip, tar.gz, bz2, tar sind verfügbar) und diese zu entpacken (gz, tar.gz, tar, rar, zip, 7z).

      Probleme:
      Wir hatten viele Probleme mit "echten" Archiven cp866 (DOS) -codierte Dateinamen und umgekehrte Schrägstriche in Dateinamen (Windows). Einige Bibliotheken (Standard ZipFile python3, python-libarchive) funktionierten nicht mit russischen Namen im Archiv. Einige Bibliotheksimplementierungen, insbesondere SevenZip, RarFile, können leere Ordner und Dateien nicht entpacken (sie befinden sich ständig in Archiven mit CMS). Benutzer möchten auch immer sehen, wie der Vorgang ausgeführt wird, aber wie kann dies geschehen, wenn die Bibliothek dies nicht zulässt (z. B. wird der Aufruf von extractall () einfach ausgeführt)?

      Lösung:
      Die Bibliotheken ZipFile sowie Libarchive-Python mussten repariert und als separate Pakete mit dem Projekt verbunden werden. Für libarchive-python musste ich die Bibliothek zerlegen und an Python 3 anpassen.

      Das Erstellen von Dateien und Ordnern mit der Größe Null (ein Fehler in den Bibliotheken SevenZip und RarFile wurde festgestellt) musste in einem separaten Zyklus ganz am Anfang der Dateikopfzeilen im Archiv erfolgen. Für alle Fehler wurden die Entwickler abgemeldet, da wir die Zeit finden, werden wir ihnen eine Pull-Anfrage senden, anscheinend werden sie es nicht selbst beheben.

      Separat wurde die gzip-Verarbeitung von komprimierten Dateien durchgeführt (für SQL-Dumps usw.), es gab keine Krücken unter Verwendung der Standardbibliothek.

      Der Operationsfortschritt wird mithilfe eines IN_CREATE-Systemaufrufs über Nacht mithilfe der pyinotify- Bibliothek überwacht. Es funktioniert natürlich nicht sehr genau (es funktioniert nicht immer, wenn die Dateien stark verschachtelt sind, also wird ein magischer Faktor von 1,5 hinzugefügt), aber es hat die Aufgabe, mindestens etwas Ähnliches für Benutzer anzuzeigen. Eine gute Lösung, da es keine Möglichkeit gibt, dies zu verfolgen, ohne alle Bibliotheken für Archive neu zu schreiben.

      Code zum Entpacken und Erstellen von Archiven.

      Beispielcode mit Kommentaren
      # Пример работы скрипта для распаковки ахивов
      # Мы не ожидали, что везде придется вносить костыли, работа с русскими буквами, архивы windows и т.д
      # Сами библиотеки также нуждались в доработке, в том числе и стандартная ZipFile из python 3
      from lib.FileManager.ZipFile import ZipFile, is_zipfile
      from lib.FileManager.LibArchiveEntry import Entry
      if is_zipfile(abs_archive_path):
          self.logger.info("Archive ZIP type, using zipfile (beget)")
          a = ZipFile(abs_archive_path)
      elif rarfile.is_rarfile(abs_archive_path):
          self.logger.info("Archive RAR type, using rarfile")
          a = rarfile.RarFile(abs_archive_path)
      else:
          self.logger.info("Archive 7Zip type, using py7zlib")
          a = SevenZFile(abs_archive_path)
          # Бибилиотека не распаковывает файлы, если они пусты (не создает пустые файлы и папки)
          for fileinfo in a.archive.header.files.files:
              if not fileinfo['emptystream']:
                  continue
              name = fileinfo['filename']
              # Костыли для windows - архивов
              try:
                  unicode_name = name.encode('UTF-8').decode('UTF-8')
              except UnicodeDecodeError:
                  unicode_name = name.encode('cp866').decode('UTF-8')
              unicode_name = unicode_name.replace('\\', '/')  # For windows name in rar etc.
              file_name = os.path.join(abs_extract_path, unicode_name)
              dir_name = os.path.dirname(file_name)
              if not os.path.exists(dir_name):
                  os.makedirs(dir_name)
              if os.path.exists(dir_name) and not os.path.isdir(dir_name):
                  os.remove(dir_name)
                  os.makedirs(dir_name)
              if os.path.isdir(file_name):
                  continue
              f = open(file_name, 'w')
              f.close()
      infolist = a.infolist()
      # Далее алгоритм отличается по скорости. В зависимости от того есть ли 
      # not-ascii имена файлов - выполним по файлам, а если нет вызовем extractall()
      # Классическая проверка
      not_ascii = False
      try:
          abs_extract_path.encode('utf-8').decode('ascii')
          for name in a.namelist():
              name.encode('utf-8').decode('ascii')
      except UnicodeDecodeError:
          not_ascii = True
      except UnicodeEncodeError:
          not_ascii = True
      # ==========
      # Алгоритм по распаковке скрыт для краткости - там ничего интересного
      # ==========
      t = threading.Thread(target=self.progress, args=(infolist, self.extracted_files, abs_extract_path))
      t.daemon = True
      t.start()
      # Прогресс операции отслеживается с помошью вотчера на системный вызов IN_CREATE 
      # Неплохое решение, учитывая, что нет возможности отследить это, не переписывая все библиотеки для архивов
      def progress(self, infolist, progress, extract_path):
          self.logger.debug("extract thread progress() start")
          next_tick = time.time() + REQUEST_DELAY
          # print pprint.pformat("Clock = %s ,  tick = %s" % (str(time.time()), str(next_tick)))
          progress["count"] = 0
          class Identity(pyinotify.ProcessEvent):
              def process_default(self, event):
                  progress["count"] += 1
                  # print("Has event %s progress %s" % (repr(event), pprint.pformat(progress)))
          wm1 = pyinotify.WatchManager()
          wm1.add_watch(extract_path, pyinotify.IN_CREATE, rec=True, auto_add=True)
          s1 = pyinotify.Stats()  # Stats is a subclass of ProcessEvent
          notifier1 = pyinotify.ThreadedNotifier(wm1, default_proc_fun=Identity(s1))
          notifier1.start()
          total = float(len(infolist))
          while not progress["done"]:
              if time.time() > next_tick:
                  count = float(progress["count"]) * 1.5
                  if count <= total:
                      op_progress = {
                          'percent': round(count / total, 2),
                          'text': str(int(round(count / total, 2) * 100)) + '%'
                      }
                  else:
                      op_progress = {
                          'percent': round(99, 2),
                          'text': '99%'
                      }
                  self.on_running(self.status_id, progress=op_progress, pid=self.pid, pname=self.name)
                  next_tick = time.time() + REQUEST_DELAY
                  time.sleep(REQUEST_DELAY)
          # иначе пользователям кажется, что распаковалось не полностью
          op_progress = {
              'percent': round(99, 2),
              'text': '99%'
          }
          self.on_running(self.status_id, progress=op_progress, pid=self.pid, pname=self.name)
          time.sleep(REQUEST_DELAY)
          notifier1.stop()


    • Erhöhte Sicherheitsanforderungen


      Aufgabe:
      Geben Sie dem Benutzer nicht die Möglichkeit, auf den Zielserver zuzugreifen

      Probleme:
      Jeder weiß, dass sich Hunderte von Sites und Benutzern gleichzeitig auf dem Hostserver befinden können. In den ersten Versionen unseres Produkts konnten Mitarbeiter einige Vorgänge mit Root-Rechten ausführen. In einigen Fällen war es theoretisch (wahrscheinlich) möglich, auf Dateien und Ordner anderer Benutzer zuzugreifen, zu viel zu lesen oder etwas zu beschädigen.

      Leider können wir keine konkreten Beispiele nennen, da es Fehler gab, die jedoch den Server insgesamt nicht betrafen und mehr unsere Fehler als eine Sicherheitslücke darstellten. In jedem Fall gibt es im Rahmen der Hosting-Infrastruktur Möglichkeiten zur Reduzierung der Last und Überwachung, und in der Version für OpenSource haben wir uns entschlossen, die Sicherheit erheblich zu verbessern.

      Lösung:
      Alle Operationen wurden in den sogenannten Workern (createFile, extractArchive, findText) usw. ausgeführt. Jeder Mitarbeiter führt vor Arbeitsbeginn die PAM-Authentifizierung sowie die Einstellungs-ID des Benutzers durch.

      In diesem Fall arbeiten alle Mitarbeiter in einem separaten Prozess und unterscheiden sich nur in Wrappern (wir warten oder warten nicht auf eine Antwort). Selbst wenn der Algorithmus zum Ausführen eines bestimmten Vorgangs eine Sicherheitsanfälligkeit enthält, liegt daher eine Isolation auf der Ebene der Systemrechte vor.

      Die Anwendungsarchitektur erlaubt auch keinen direkten Zugriff auf das Dateisystem, beispielsweise über einen Webserver. Mit dieser Lösung können Sie die Auslastung effektiv berücksichtigen und die Benutzeraktivitäten auf dem Server mit Mitteln von Drittanbietern überwachen.

    Installation


    Wir gingen den Weg des geringsten Widerstands und bereiteten Docker-Images vor, anstatt sie manuell zu installieren. Die Installation erfolgt im Wesentlichen über mehrere Befehle:

    user@host:~$ wget https://raw.githubusercontent.com/LTD-Beget/sprutio/master/run.sh
    user@host:~$ chmod +x run.sh
    user@host:~$ ./run.sh

    run.sh überprüft, ob Bilder heruntergeladen wurden, und startet 5 Container mit Systemkomponenten. Um Bilder zu aktualisieren, müssen Sie ausführen

    user@host:~$ ./run.sh pull


    Das Stoppen und Löschen von Bildern erfolgt jeweils über die Parameter stop und rm. Die Assembly-Docking-Datei befindet sich im Projektcode, die Assembly dauert 10-20 Minuten.
    Wie Sie in naher Zukunft die Entwicklungsumgebung verbessern können, erfahren Sie auf der Website und im Wiki auf Github .

    Helfen Sie uns, Sprut.IO zu verbessern


    Es gibt viele offensichtliche Möglichkeiten zur weiteren Verbesserung des Dateimanagers.

    Als das nützlichste für Benutzer sehen wir:

    • SSH / SFTP-Unterstützung hinzufügen
    • WebDav-Unterstützung hinzufügen
    • Terminal hinzufügen
    • Füge die Fähigkeit hinzu, mit Git zu arbeiten
    • Fügen Sie die Möglichkeit hinzu, Dateien freizugeben
    • Hinzufügen von wechselnden Themen Design und Erstellung von verschiedenen Themen
    • Erstellen Sie eine universelle Schnittstelle für die Arbeit mit Modulen


    Wenn Sie Add-Ons haben, die für Nutzer nützlich sein können, teilen Sie uns diese in den Kommentaren oder auf der Mailingliste sprutio-ru@groups.google.com mit .

    Wir werden anfangen, sie umzusetzen, aber ich werde keine Angst haben, dies zu sagen: Für uns allein wird es Jahre, wenn nicht Jahrzehnte dauern . Wenn Sie Programmieren lernen möchten , Python und ExtJS kennen und in einem offenen Projekt Entwicklungserfahrung sammeln möchten, laden wir Sie ein, sich an der Entwicklung von Sprut.IO zu beteiligen. Außerdem zahlen wir für jedes realisierte Feature eine Gebühr, da wir es nicht selbst implementieren müssen.

    ToDo - Liste und der Status der Aufgaben in dem auf der Website des Projektes zu sehen TODO .

    Vielen Dank für Ihre Aufmerksamkeit! Wenn es interessant ist, dann schreiben wir gerne noch mehr Details über die Organisation des Projekts und beantworten Ihre Fragen in den Kommentaren.

    Projektseite: https://sprut.io
    Demo ist verfügbar unter: https://demo.sprut.io:9443
    Quellcode: https://github.com/LTD-Beget/sprutio
    Russische Mailingliste: sprutio-ru @ groups.google.com
    Englische Mailingliste: sprutio@groups.google.com

    Jetzt auch beliebt: