Linux ma wiele twarzy: jak pracować na dowolnej dystrybucji

Linux ma wiele twarzy: jak pracować na dowolnej dystrybucji

Stworzenie aplikacji do tworzenia kopii zapasowych, która działa na dowolnej dystrybucji, nie jest łatwym zadaniem. Aby mieć pewność, że narzędzie Veeam Agent dla systemu Linux będzie działać w dystrybucjach od Red Hat 6 i Debian 6 po OpenSUSE 15.1 i Ubuntu 19.04, należy rozwiązać szereg problemów, zwłaszcza biorąc pod uwagę, że oprogramowanie zawiera moduł jądra.

Artykuł powstał na podstawie materiałów z wystąpienia na konferencji Linux Piotr 2019.

Linux to nie tylko jeden z najpopularniejszych systemów operacyjnych. Zasadniczo jest to platforma, na podstawie której można stworzyć coś wyjątkowego, coś własnego. Dzięki temu Linux ma wiele dystrybucji różniących się zestawem komponentów oprogramowania. I tu pojawia się problem: aby oprogramowanie mogło działać w dowolnej dystrybucji, należy wziąć pod uwagę cechy każdej z nich.

Menedżerowie pakietów. .deb kontra .rpm

Zacznijmy od oczywistego problemu dystrybucji produktu w różnych dystrybucjach.
Najbardziej typowym sposobem dystrybucji oprogramowania jest umieszczenie pakietu w repozytorium, aby wbudowany w system menedżer pakietów mógł go stamtąd zainstalować.
Mamy jednak dwa popularne formaty pakietów: rpm и deb. Oznacza to, że wszyscy będą musieli wesprzeć.

W świecie pakietów deb poziom kompatybilności jest niesamowity. Ten sam pakiet instaluje się i działa równie dobrze zarówno na Debianie 6, jak i Ubuntu 19.04. Standardy procesu budowania pakietów i pracy z nimi, określone w starych dystrybucjach Debiana, pozostają aktualne w nowomodnej Linux Mint i podstawowym systemie operacyjnym. Dlatego w przypadku Veeam Agent dla Linuksa wystarczy jeden pakiet deb na każdą platformę sprzętową.

Ale w świecie pakietów RPM różnice są ogromne. Po pierwsze ze względu na fakt, że istnieje dwóch całkowicie niezależnych dystrybutorów, Red Hat i SUSE, dla których kompatybilność jest zupełnie zbędna. Po drugie, ci dystrybutorzy mają od nich zestawy dystrybucyjne. wsparcie i eksperymenty. Nie ma również potrzeby zapewnienia kompatybilności między nimi. Okazało się, że el6, el7 i el8 mają własne pakiety. Oddzielny pakiet dla Fedory. Pakiety dla SLES11 i 12 oraz osobny dla openSUSE. Głównym problemem są zależności i nazwy pakietów.

Problem zależności

Niestety te same pakiety często trafiają pod różnymi nazwami w różnych dystrybucjach. Poniżej znajduje się częściowa lista zależności pakietów Veeam.

Dla EL7:
Dla SLES 12:

  • biblioteka
  • libgcc
  • libstdc++
  • ncurses-libs
  • biblioteki bezpieczników
  • biblioteki plików
  • veeamsnap=3.0.2.1185
  • liblkid1
  • libgcc_s1
  • libstdc ++ 6
  • bibliotekamagic1
  • libfuse2
  • veeamsnap-kmp=3.0.2.1185

W efekcie lista zależności jest unikalna dla danej dystrybucji.

Gorzej jest, gdy zaktualizowana wersja zaczyna ukrywać się pod starą nazwą pakietu.

Przykład:

Pakiet został zaktualizowany w Fedorze 24 przekleństwa z wersji 5 do wersji 6. Nasz produkt został zbudowany w wersji 5, aby zapewnić kompatybilność ze starszymi dystrybucjami. Aby użyć starej, piątej wersji biblioteki w Fedorze 5, musiałem użyć pakietu ncurses-compat-libs.

W rezultacie istnieją dwa pakiety dla Fedory z różnymi zależnościami.

Dalej ciekawiej. Po kolejnej aktualizacji dystrybucji pakiet ncurses-compat-libs w wersji 5 biblioteki okazuje się, że jest ona niedostępna. Dla dystrybutora przeciąganie starych bibliotek do nowej wersji dystrybucji jest kosztowne. Po pewnym czasie problem powtórzył się w dystrybucjach SUSE.

W rezultacie niektóre dystrybucje musiały porzucić swoją wyraźną zależność od ncurses-libsi napraw produkt tak, aby mógł współpracować z dowolną wersją biblioteki.

Nawiasem mówiąc, w wersji 8 Red Hata nie ma już metapakietu pyton, który nawiązywał do starego dobrego python 2.7. Jest python2 и pyton3.

Alternatywa dla menedżerów pakietów

Problem z zależnościami jest stary i od dawna oczywisty. Pamiętaj tylko o piekle zależności.
Łączenie różnych bibliotek i aplikacji tak, aby wszystkie działały stabilnie i nie powodowały konfliktów - w rzeczywistości jest to zadanie, które próbuje rozwiązać każdy dystrybutor Linuksa.

Menedżer pakietów próbuje rozwiązać ten problem w zupełnie inny sposób. Żwawy z Canonicala. Główna idea: aplikacja działa w izolowanym i chronionym od głównego systemu piaskownicy. Jeśli aplikacja wymaga bibliotek, są one dostarczane wraz z samą aplikacją.

Flatpak umożliwia także uruchamianie aplikacji w piaskownicy przy użyciu kontenerów Linux. Wykorzystywany jest także pomysł piaskownicy AppImage.

Rozwiązania te pozwalają na stworzenie jednego pakietu dla dowolnej dystrybucji. W przypadku Flatpak instalacja i uruchomienie aplikacji jest możliwe nawet bez wiedzy administratora.

Głównym problemem jest to, że nie wszystkie aplikacje można uruchomić w piaskownicy. Niektóre osoby potrzebują bezpośredniego dostępu do platformy. Już nawet nie mówię o modułach jądra, które są ściśle zależne od jądra i nie mieszczą się w koncepcji sandboksa.

Drugi problem polega na tym, że popularne w środowisku korporacyjnym dystrybucje firm Red Hat i SUSE nie zawierają jeszcze wsparcia dla Snappy i Flatpak.

W związku z tym narzędzie Veeam Agent dla systemu Linux nie jest dostępne snapcraft.io zupełnie nie www.flathub.org.

Na zakończenie pytania o menedżery pakietów chciałbym zauważyć, że istnieje możliwość całkowitej rezygnacji z menedżerów pakietów poprzez połączenie plików binarnych i skryptu do ich instalacji w jednym pakiecie.

Taki pakiet pozwala stworzyć jeden wspólny pakiet dla różnych dystrybucji i platform, przeprowadzić interaktywny proces instalacji, przeprowadzając niezbędne dostosowania. Z takimi pakietami dla Linuksa spotkałem się jedynie od VMware.

Aktualizacja problemu

Linux ma wiele twarzy: jak pracować na dowolnej dystrybucji
Nawet jeśli wszystkie problemy z zależnościami zostaną rozwiązane, program może działać inaczej w tej samej dystrybucji. To kwestia aktualizacji.

Istnieją 3 strategie aktualizacji:

  • Najprostszym sposobem jest nigdy nie aktualizować. Założyłem serwer i zapomniałem o nim. Po co aktualizować, jeśli wszystko działa? Problemy zaczynają się już przy pierwszym kontakcie z pomocą techniczną. Twórca dystrybucji obsługuje tylko zaktualizowaną wersję.
  • Możesz zaufać dystrybutorowi i skonfigurować automatyczne aktualizacje. W takim przypadku wezwanie pomocy technicznej nastąpi prawdopodobnie natychmiast po nieudanej aktualizacji.
  • Opcja ręcznej aktualizacji dopiero po uruchomieniu na infrastrukturze testowej jest najbardziej niezawodna, ale droga i czasochłonna. Nie każdego na to stać.

Ponieważ różni użytkownicy stosują różne strategie aktualizacji, konieczna jest obsługa zarówno najnowszej wersji, jak i wszystkich wydanych wcześniej. Komplikuje to zarówno proces programowania, jak i testowania, a także dodaje ból głowy zespołowi wsparcia.

Różnorodność platform sprzętowych

Różne platformy sprzętowe to problem w dużej mierze specyficzny dla kodu natywnego. Musisz przynajmniej zebrać pliki binarne dla każdej obsługiwanej platformy.

W projekcie Veeam Agent dla systemu Linux nadal nie możemy obsługiwać niczego takiego jak ten RISC.

Nie będę się szczegółowo rozwodzić nad tym zagadnieniem. Zarysuję tylko główne problemy: typy zależne od platformy, takie jak size_t, wyrównanie struktury i kolejność bajtów.

Łączenie statyczne i/lub dynamiczne

Linux ma wiele twarzy: jak pracować na dowolnej dystrybucji
Pytanie jednak brzmi: „Jak połączyć się z bibliotekami – dynamicznie czy statycznie?” warto omówić.

Z reguły aplikacje C/C++ pod Linuksem korzystają z łączenia dynamicznego. Działa to świetnie, jeśli aplikacja jest zbudowana specjalnie dla konkretnej dystrybucji.

Jeśli zadaniem jest pokrycie różnych dystrybucji jednym plikiem binarnym, wówczas należy skupić się na najstarszej obsługiwanej dystrybucji. Dla nas jest to Red Hat 6. Zawiera gcc 4.4, którego nawet standard C++11 nie obsługuje całkowite.

Nasz projekt budujemy przy użyciu gcc 6.3, który w pełni obsługuje C++ 14. Naturalnie w tym przypadku na Red Hat 6 musisz mieć przy sobie bibliotekę libstdc++ i wzmacniać biblioteki. Najłatwiej jest połączyć się z nimi statycznie.

Ale niestety nie wszystkie biblioteki można łączyć statycznie.

Po pierwsze, biblioteki systemowe takie jak libfuse, biblioteka konieczne jest dynamiczne łączenie, aby zapewnić ich kompatybilność z jądrem i jego modułami.

Po drugie, istnieje subtelność w przypadku licencji.

Licencja GPL w zasadzie pozwala na łączenie bibliotek wyłącznie z kodem open source. MIT i BSD umożliwiają statyczne łączenie i umożliwiają włączenie bibliotek do projektu. Wydaje się jednak, że LGPL nie sprzeciwia się łączeniu statycznemu, ale wymaga udostępniania plików niezbędnych do połączenia.

Ogólnie rzecz biorąc, użycie dynamicznego linkowania zapobiegnie konieczności podawania czegokolwiek.

Tworzenie aplikacji C/C++

Aby zbudować aplikacje C/C++ dla różnych platform i dystrybucji, wystarczy wybrać lub zbudować odpowiednią wersję gcc i użyć kompilatorów krzyżowych dla konkretnych architektur i złożyć cały zestaw bibliotek. Ta praca jest całkiem wykonalna, ale dość kłopotliwa. Nie ma też gwarancji, że wybrany kompilator i biblioteki zapewnią działającą wersję.

Oczywista zaleta: infrastruktura jest znacznie uproszczona, ponieważ cały proces kompilacji można przeprowadzić na jednej maszynie. Dodatkowo wystarczy zebrać jeden zestaw plików binarnych dla jednej architektury i można je spakować w pakiety dla różnych dystrybucji. W ten sposób tworzone są pakiety Veeam dla narzędzia Veeam Agent dla systemu Linux.

W przeciwieństwie do tej opcji, można po prostu przygotować build farmę, czyli kilka maszyn do montażu. Każda taka maszyna zapewni kompilację aplikacji i montaż pakietów dla określonej dystrybucji i określonej architektury. W tym przypadku kompilacja odbywa się przy pomocy środków przygotowanych przez dystrybutora. Oznacza to, że wyeliminowany zostaje etap przygotowania kompilatora i wybierania bibliotek. Ponadto proces kompilacji można łatwo zrównoleglić.

Takie podejście ma jednak wadę: dla każdej dystrybucji w ramach tej samej architektury będziesz musiał zebrać własny zestaw plików binarnych. Kolejną wadą jest konieczność obsługi tak dużej liczby maszyn oraz przydzielania dużej ilości miejsca na dysku i pamięci RAM.

W ten sposób kompilowane są pakiety KMOD modułu jądra veeamsnap dla dystrybucji Red Hat.

Otwórz usługę kompilacji

Koledzy z SUSE próbowali wdrożyć jakiś złoty środek w postaci specjalnej usługi do kompilowania aplikacji i asemblowania pakietów - usługa openbuild.

Zasadniczo jest to hypervisor, który tworzy maszynę wirtualną, instaluje w niej wszystkie niezbędne pakiety, kompiluje aplikację i buduje pakiet w tym izolowanym środowisku, po czym maszyna wirtualna jest wypuszczana.

Linux ma wiele twarzy: jak pracować na dowolnej dystrybucji

Harmonogram zaimplementowany w OpenBuildService określi, ile maszyn wirtualnych może uruchomić w celu uzyskania optymalnej szybkości budowania pakietu. Wbudowany mechanizm podpisywania będzie podpisywał pakiety i przesyłał je do wbudowanego repozytorium. Wbudowany system kontroli wersji zapisuje historię zmian i kompilacji. Pozostaje tylko dodać źródła do tego systemu. Nie musisz nawet samodzielnie konfigurować serwera, możesz użyć otwartego.

Jest jednak problem: taki kombajn trudno zmieścić w istniejącej infrastrukturze. Na przykład kontrola wersji nie jest potrzebna, mamy już własną dla kodów źródłowych. Nasz mechanizm podpisu jest inny: używamy specjalnego serwera. Repozytorium również nie jest potrzebne.

Poza tym obsługa innych dystrybucji – np. Red Hata – jest zaimplementowana dość słabo, co jest zrozumiałe.

Zaletą takiej usługi jest szybkie wsparcie dla kolejnej wersji dystrybucji SUSE. Przed oficjalnym ogłoszeniem wydania pakiety niezbędne do montażu umieszczane są w publicznym repozytorium. Na liście dostępnych dystrybucji w OpenBuildService pojawi się nowa. Zaznaczamy pole i zostaje ono dodane do planu budowy. Tym samym dodanie nowej wersji dystrybucji odbywa się niemal jednym kliknięciem.

W naszej infrastrukturze, korzystając z OpenBuildService, składana jest cała gama pakietów KMP modułu jądra veeamsnap dla dystrybucji SUSE.

Następnie chciałbym poruszyć kwestie specyficzne dla modułów jądra.

ABI jądra

Moduły jądra Linuksa były w przeszłości dystrybuowane w formie źródłowej. Faktem jest, że twórcy jądra nie obarczają się troską o wsparcie stabilnego API dla modułów jądra, a zwłaszcza na poziomie binarnym, zwanego dalej kABI.

Aby zbudować moduł dla jądra waniliowego, zdecydowanie potrzebujesz nagłówków tego konkretnego jądra i będzie to działać tylko na tym jądrze.

DKMS pozwala zautomatyzować proces budowania modułów podczas aktualizacji jądra. W rezultacie użytkownicy repozytorium Debiana (i jego wielu krewnych) korzystają z modułów jądra pochodzących albo z repozytorium dystrybutora, albo skompilowanych ze źródeł przy użyciu DKMS.

Jednak sytuacja ta nie szczególnie odpowiada segmentowi Enterprise. Dystrybutorzy zastrzeżonego kodu chcą dystrybuować produkt w postaci skompilowanych plików binarnych.

Administratorzy nie chcą przechowywać narzędzi programistycznych na serwerach produkcyjnych ze względów bezpieczeństwa. Dystrybutorzy Linuksa dla przedsiębiorstw, tacy jak Red Hat i SUSE, zdecydowali, że będą mogli wspierać stabilne kABI dla swoich użytkowników. Rezultatem były pakiety KMOD dla Red Hat i pakiety KMP dla SUSE.

Istota tego rozwiązania jest dość prosta. W przypadku określonej wersji dystrybucji API jądra jest zablokowane. Dystrybutor twierdzi, że korzysta z jądra np. 3.10 i wprowadza jedynie poprawki i ulepszenia, które nie mają wpływu na interfejsy jądra, a moduły zebrane dla pierwszego jądra można wykorzystać do wszystkich kolejnych bez rekompilacji.

Red Hat twierdzi, że dystrybucja jest zgodna z kABI przez cały cykl jej życia. Oznacza to, że zmontowany moduł dla rhel 6.0 (wydanie z listopada 2010 r.) powinien również działać w wersji 6.10 (wydanie z czerwca 2018 r.). A to już prawie 8 lat. Oczywiście to zadanie jest dość trudne.
Odnotowaliśmy kilka przypadków, w których moduł Veeamsnap przestał działać z powodu problemów ze zgodnością z kABI.

Po tym jak moduł veeamsnap skompilowany dla RHEL 7.0 okazał się niekompatybilny z jądrem z RHEL 7.5, ale załadował się i gwarantowało awarię serwera, całkowicie zrezygnowaliśmy ze stosowania kompatybilności kABI dla RHEL 7.

Obecnie pakiet KMOD dla RHEL 7 zawiera zestaw dla każdej wersji wydania oraz skrypt ładujący moduł.

Firma SUSE ostrożniej podeszła do zadania zapewnienia kompatybilności z kABI. Zapewniają kompatybilność z kABI tylko w ramach jednego dodatku Service Pack.

Na przykład wydanie SLES 12 miało miejsce we wrześniu 2014 r. A SLES 12 SP1 było już w grudniu 2015 r., czyli minęło nieco ponad rok. Mimo że obydwa wydania korzystają z jądra 3.12, są one niezgodne z kABI. Oczywiście utrzymanie kompatybilności z kABI przez zaledwie rok jest znacznie łatwiejsze. Coroczny cykl aktualizacji modułów jądra nie powinien sprawić problemów twórcom modułów.

W wyniku tej polityki SUSE nie odnotowaliśmy ani jednego problemu ze zgodnością kABI w naszym module veeamsnap. To prawda, że ​​​​liczba pakietów dla SUSE jest prawie o rząd wielkości większa.

Patche i backporty

Chociaż dystrybutorzy starają się zapewnić kompatybilność z kABI i stabilność jądra, starają się także poprawić wydajność i wyeliminować defekty tego stabilnego jądra.

Jednocześnie, oprócz własnej „pracy nad błędami”, twórcy korporacyjnego jądra Linuksa monitorują zmiany w jądrze waniliowym i przenoszą je na „stabilne”.

Czasami prowadzi to do nowych błędy.

W najnowszej wersji Red Hat 6 popełniono błąd w jednej z mniejszych aktualizacji. Doprowadziło to do tego, że moduł Veeamsnap gwarantował awarię systemu po wydaniu migawki. Po porównaniu źródeł jądra przed i po aktualizacji dowiedzieliśmy się, że winny był backport. Podobną poprawkę wprowadzono w jądrze waniliowym w wersji 4.19. Tyle, że ta poprawka działała dobrze w jądrze waniliowym, ale podczas przenoszenia jej do „stabilnej” wersji 2.6.32 pojawił się problem z blokadą.

Oczywiście każdemu zawsze zdarzają się błędy, ale czy warto było przeciągać kod z 4.19 na 2.6.32, ryzykując stabilność?.. Nie jestem pewien...

Najgorsze jest to, gdy marketing wdaje się w przeciąganie liny pomiędzy „stabilnością” a „modernizacją”. Dział marketingu potrzebuje, aby rdzeń zaktualizowanej dystrybucji był z jednej strony stabilny, a jednocześnie lepszy pod względem wydajności i miał nowe funkcje. Prowadzi to do dziwnych kompromisów.

Kiedy próbowałem zbudować moduł na jądrze 4.4 z SLES 12 SP3, byłem zaskoczony, gdy znalazłem w nim funkcjonalność z wanilii 4.8. Moim zdaniem implementacja blokowego wejścia/wyjścia jądra 4.4 z SLES 12 SP3 jest bardziej podobna do jądra 4.8 niż poprzednia wersja stabilnego jądra 4.4 z SLES12 SP2. Nie mogę ocenić, jaki procent kodu został przeniesiony z jądra 4.8 do SLES 4.4 dla SP3, ale nie mogę nawet nazwać jądra tym samym stabilnym 4.4.

Najbardziej nieprzyjemną rzeczą jest to, że pisząc moduł, który działałby równie dobrze na różnych jądrach, nie można już polegać na wersji jądra. Trzeba też wziąć pod uwagę dystrybucję. Dobrze, że czasami można się zaangażować w definicję, która pojawia się wraz z nową funkcjonalnością, jednak nie zawsze taka możliwość się pojawia.

W rezultacie kod zostaje przerośnięty dziwnymi dyrektywami kompilacji warunkowej.

Istnieją również łatki zmieniające udokumentowane API jądra.
Natknąłem się na dystrybucję Neon KDE 5.16 i byłem bardzo zaskoczony, widząc, że wywołanie lookup_bdev w tej wersji jądra zmieniło listę parametrów wejściowych.

Aby to połączyć, musiałem dodać skrypt do pliku makefile, który sprawdza, czy funkcja lookup_bdev ma parametr mask.

Podpisywanie modułów jądra

Wróćmy jednak do kwestii dystrybucji pakietów.

Jedną z zalet stabilnego kABI jest to, że moduły jądra można podpisywać jako plik binarny. W takim przypadku programista może być pewien, że moduł nie został przypadkowo uszkodzony lub celowo zmodyfikowany. Możesz to sprawdzić za pomocą polecenia modinfo.

Dystrybucje Red Hat i SUSE umożliwiają sprawdzenie sygnatury modułu i załadowanie go tylko wtedy, gdy w systemie zarejestrowany jest odpowiedni certyfikat. Certyfikat jest kluczem publicznym, którym podpisany jest moduł. Dystrybuujemy go jako oddzielny pakiet.

Problem polega na tym, że certyfikaty mogą być albo wbudowane w jądro (używają ich dystrybutorzy), albo muszą zostać zapisane w nieulotnej pamięci EFI za pomocą narzędzia mokutil. Pożytek mokutil Podczas instalacji certyfikatu wymaga ponownego uruchomienia systemu i jeszcze przed załadowaniem jądra systemu operacyjnego wyświetla monit administratora o zezwolenie na załadowanie nowego certyfikatu.

Dlatego dodanie certyfikatu wymaga fizycznego dostępu administratora do systemu. Jeśli maszyna znajduje się gdzieś w chmurze lub po prostu w zdalnej serwerowni i dostęp odbywa się tylko przez sieć (na przykład przez ssh), to dodanie certyfikatu nie będzie możliwe.

EFI na maszynach wirtualnych

Pomimo tego, że EFI od dawna jest wspierany przez prawie wszystkich producentów płyt głównych, instalując system, administrator może nie myśleć o potrzebie EFI i może zostać wyłączony.

Nie wszystkie hypervisory obsługują EFI. VMWare vSphere obsługuje EFI począwszy od wersji 5.
Microsoft Hyper-V zyskał także obsługę EFI, począwszy od Hyper-V dla Windows Server 2012R2.

Jednakże w domyślnej konfiguracji ta funkcjonalność jest wyłączona dla komputerów z systemem Linux, co oznacza, że ​​nie można zainstalować certyfikatu.

W vSphere 6.5 ustaw tę opcję Bezpieczne Boot możliwe tylko w starej wersji interfejsu internetowego, który działa poprzez Flash. Interfejs sieciowy w HTML-5 jest wciąż daleko w tyle.

Dystrybucje eksperymentalne

Na koniec rozważmy kwestię dystrybucji eksperymentalnych i dystrybucji bez oficjalnego wsparcia. Z jednej strony jest mało prawdopodobne, aby takie dystrybucje można było znaleźć na serwerach poważnych organizacji. Nie ma oficjalnego wsparcia dla takich dystrybucji. Dlatego je zapewnij. Produkt nie może być obsługiwany w takiej dystrybucji.

Jednak takie dystrybucje stają się wygodną platformą do testowania nowych, eksperymentalnych rozwiązań. Na przykład Fedora, OpenSUSE Tumbleweed lub niestabilne wersje Debiana. Są dość stabilne. Zawsze mają nowe wersje programów i zawsze nowe jądro. Za rok ta eksperymentalna funkcjonalność może trafić do zaktualizowanych wersji RHEL, SLES lub Ubuntu.

Jeśli więc coś nie działa w dystrybucji eksperymentalnej, jest to powód, aby znaleźć problem i go rozwiązać. Trzeba być przygotowanym na to, że już niedługo ta funkcjonalność pojawi się na serwerach produkcyjnych użytkowników.

Możesz zapoznać się z aktualną listą oficjalnie wspieranych dystrybucji dla wersji 3.0 tutaj. Ale rzeczywista lista dystrybucji, na których może działać nasz produkt, jest znacznie szersza.

Mnie osobiście zainteresował eksperyment z systemem operacyjnym Elbrus. Po sfinalizowaniu pakietu Veeam nasz produkt został zainstalowany i zaczął działać. O tym eksperymencie pisałam na Habré w Artykuł.

Cóż, wsparcie dla nowych dystrybucji trwa. Czekamy na wydanie wersji 4.0. Beta wkrótce się pojawi, więc miej oko co nowego!

Źródło: www.habr.com

Dodaj komentarz