Linux memiliki banyak wajah: cara bekerja pada distribusi apa pun

Linux memiliki banyak wajah: cara bekerja pada distribusi apa pun

Membuat aplikasi cadangan yang berfungsi pada distribusi apa pun bukanlah tugas yang mudah. Untuk memastikan Veeam Agent untuk Linux berfungsi pada distribusi dari Red Hat 6 dan Debian 6, hingga OpenSUSE 15.1 dan Ubuntu 19.04, Anda harus menyelesaikan berbagai masalah, terutama mengingat produk perangkat lunak menyertakan modul kernel.

Artikel ini dibuat berdasarkan materi pidato pada konferensi tersebut Linux Peter 2019.

Linux bukan hanya salah satu sistem operasi paling populer. Pada dasarnya, ini adalah platform yang menjadi dasar Anda dapat membuat sesuatu yang unik, milik Anda sendiri. Berkat ini, Linux memiliki banyak distribusi yang berbeda dalam kumpulan komponen perangkat lunaknya. Dan di sini muncul masalah: agar produk perangkat lunak berfungsi pada distribusi apa pun, Anda harus mempertimbangkan fitur masing-masingnya.

Manajer paket. .deb vs .rpm

Mari kita mulai dengan masalah yang jelas dalam mendistribusikan produk ke berbagai distribusi.
Cara paling umum untuk mendistribusikan produk perangkat lunak adalah dengan meletakkan paket pada repositori sehingga manajer paket yang ada di dalam sistem dapat menginstalnya dari sana.
Namun, kami memiliki dua format paket populer: rpm и deb. Ini berarti semua orang harus mendukungnya.

Dalam dunia paket deb, tingkat kompatibilitasnya luar biasa. Paket yang sama menginstal dan bekerja dengan baik di Debian 6 dan Ubuntu 19.04. Standar untuk proses pembuatan paket dan pengerjaannya, yang ditetapkan dalam distribusi Debian lama, tetap relevan di Linux Mint bermodel baru dan OS dasar. Oleh karena itu, dalam kasus Veeam Agent untuk Linux, satu paket deb untuk setiap platform perangkat keras sudah cukup.

Namun dalam dunia paket rpm, perbedaannya sangat besar. Pertama, karena terdapat dua distributor yang sepenuhnya independen, Red Hat dan SUSE, yang kompatibilitasnya sama sekali tidak diperlukan. Kedua, distributor ini memiliki kit distribusi dari mereka. dukungan dan eksperimental. Kompatibilitas di antara keduanya juga tidak diperlukan. Ternyata el6, el7 dan el8 punya paketnya masing-masing. Paket terpisah untuk Fedora. Paket untuk SLES11 dan 12 dan paket terpisah untuk openSUSE. Masalah utamanya adalah ketergantungan dan nama paket.

Masalah ketergantungan

Sayangnya, paket yang sama sering kali memiliki nama yang berbeda di distribusi yang berbeda. Di bawah ini adalah sebagian daftar dependensi paket veeam.

Untuk EL7:
Untuk SLES 12:

  • libblkid
  • libgcc
  • libstdc++
  • ncurses-libs
  • sekering-libs
  • file-libs
  • veamsnap=3.0.2.1185
  • libblkid1
  • libgcc_s1
  • libstdc ++ 6
  • libmagic1
  • libfuse2
  • veamsnap-kmp=3.0.2.1185

Hasilnya, daftar dependensi menjadi unik untuk distribusinya.

Yang lebih buruk lagi adalah ketika versi yang diperbarui mulai bersembunyi di bawah nama paket lama.

Contoh:

Paket telah diperbarui di Fedora 24 kutukan dari versi 5 hingga versi 6. Produk kami dibuat dengan versi 5 untuk memastikan kompatibilitas dengan distribusi lama. Untuk menggunakan perpustakaan versi 5 yang lama di Fedora 24, saya harus menggunakan paketnya ncurses-compat-libs.

Hasilnya, ada dua paket untuk Fedora, dengan dependensi berbeda.

Lebih jauh lagi lebih menarik. Setelah pembaruan distribusi berikutnya, paket ncurses-compat-libs dengan perpustakaan versi 5 ternyata tidak tersedia. Membutuhkan biaya yang mahal bagi distributor untuk memindahkan perpustakaan lama ke versi distribusi yang baru. Setelah beberapa waktu, masalah terulang kembali di distribusi SUSE.

Akibatnya, beberapa distribusi harus menghilangkan ketergantungan eksplisitnya ncurses-libs, dan perbaiki produk agar dapat berfungsi dengan versi perpustakaan apa pun.

Omong-omong, di Red Hat versi 8 tidak ada lagi paket meta ular sanca, yang merujuk pada masa lalu yang baik ular piton 2.7. Ada python2 и ular sanca3.

Alternatif untuk manajer paket

Masalah ketergantungan sudah lama dan sudah lama terlihat jelas. Ingat saja Ketergantungan.
Untuk menggabungkan berbagai perpustakaan dan aplikasi sehingga semuanya bekerja secara stabil dan tidak bertentangan - sebenarnya, ini adalah tugas yang coba diselesaikan oleh setiap distributor Linux.

Manajer paket mencoba menyelesaikan masalah ini dengan cara yang sangat berbeda. Tajam dari Kanonis. Ide utamanya: aplikasi berjalan di kotak pasir yang terisolasi dan terlindungi dari sistem utama. Jika suatu aplikasi memerlukan perpustakaan, perpustakaan tersebut disediakan bersama aplikasi itu sendiri.

Flatpak juga memungkinkan Anda menjalankan aplikasi di sandbox menggunakan Linux Containers. Ide kotak pasir juga digunakan AppImage.

Solusi ini memungkinkan Anda membuat satu paket untuk distribusi apa pun. Dalam kasus Flatpak instalasi dan peluncuran aplikasi dapat dilakukan bahkan tanpa sepengetahuan administrator.

Masalah utamanya adalah tidak semua aplikasi bisa berjalan di sandbox. Beberapa orang memerlukan akses langsung ke platform. Saya bahkan tidak berbicara tentang modul kernel, yang sangat bergantung pada kernel dan tidak sesuai dengan konsep sandbox.

Masalah kedua adalah distribusi perusahaan yang populer dari Red Hat dan SUSE belum mendukung Snappy dan Flatpak.

Dalam hal ini, Agen Veeam untuk Linux tidak tersedia snapcraft.io sama sekali tidak flathub.org.

Untuk menyimpulkan pertanyaan tentang pengelola paket, saya ingin mencatat bahwa ada opsi untuk meninggalkan pengelola paket sama sekali dengan menggabungkan file biner dan skrip untuk menginstalnya ke dalam satu paket.

Bundel semacam itu memungkinkan Anda membuat satu paket umum untuk distribusi dan platform berbeda, melakukan proses instalasi interaktif, dan melakukan penyesuaian yang diperlukan. Saya hanya menemukan paket seperti itu untuk Linux dari VMware.

Masalah pembaruan

Linux memiliki banyak wajah: cara bekerja pada distribusi apa pun
Meskipun semua masalah ketergantungan telah teratasi, program mungkin berjalan berbeda pada distribusi yang sama. Ini masalah pembaruan.

Ada 3 strategi pembaruan:

  • Yang paling sederhana adalah jangan pernah memperbarui. Saya mengatur server dan melupakannya. Mengapa memperbarui jika semuanya berfungsi? Masalah dimulai saat pertama kali Anda menghubungi dukungan. Pembuat distribusi hanya mendukung rilis yang diperbarui.
  • Anda dapat mempercayai distributor dan mengatur pembaruan otomatis. Dalam hal ini, panggilan ke dukungan kemungkinan akan dilakukan segera setelah pembaruan gagal.
  • Pilihan untuk memperbarui secara manual hanya setelah menjalankannya pada infrastruktur pengujian adalah yang paling dapat diandalkan, namun mahal dan memakan waktu. Tidak semua orang mampu membelinya.

Karena pengguna yang berbeda menggunakan strategi pembaruan yang berbeda, maka penting untuk mendukung rilis terbaru dan semua rilis sebelumnya. Hal ini mempersulit proses pengembangan dan pengujian serta menambah kerumitan tim dukungan.

Berbagai platform perangkat keras

Platform perangkat keras yang berbeda merupakan masalah yang sebagian besar spesifik untuk kode asli. Minimal, Anda harus mengumpulkan binari untuk setiap platform yang didukung.

Dalam proyek Veeam Agent untuk Linux, kami masih belum dapat mendukung RISC seperti ini.

Saya tidak akan membahas masalah ini secara detail. Saya hanya akan menguraikan masalah utama: tipe yang bergantung pada platform, seperti size_t, penyelarasan struktur dan urutan byte.

Tautan statis dan/atau dinamis

Linux memiliki banyak wajah: cara bekerja pada distribusi apa pun
Namun pertanyaannya adalah “Bagaimana cara menghubungkan dengan perpustakaan - secara dinamis atau statis?” layak untuk didiskusikan.

Biasanya, aplikasi C/C++ di Linux menggunakan tautan dinamis. Ini berfungsi dengan baik jika aplikasi dibuat khusus untuk distribusi tertentu.

Jika tugasnya adalah mencakup berbagai distribusi dengan satu file biner, maka Anda harus fokus pada distribusi tertua yang didukung. Bagi kami, ini adalah Red Hat 6. Ini berisi gcc 4.4, yang bahkan standar C++11 tidak mendukung sepenuhnya.

Kami membangun proyek kami menggunakan gcc 6.3, yang sepenuhnya mendukung C++14. Tentu saja, dalam hal ini, di Red Hat 6 Anda harus membawa libstdc++ dan meningkatkan perpustakaan bersama Anda. Cara termudah adalah dengan menautkannya secara statis.

Namun sayang, tidak semua perpustakaan bisa ditautkan secara statis.

Pertama, perpustakaan sistem seperti libfuse, libblkid perlu untuk menghubungkannya secara dinamis untuk memastikan kompatibilitasnya dengan kernel dan modul-modulnya.

Kedua, ada kehalusan dalam perizinan.

Lisensi GPL pada dasarnya memungkinkan Anda menghubungkan perpustakaan hanya dengan kode sumber terbuka. MIT dan BSD mengizinkan tautan statis dan mengizinkan perpustakaan untuk disertakan dalam sebuah proyek. Namun LGPL tampaknya tidak bertentangan dengan tautan statis, namun mengharuskan file yang diperlukan untuk menghubungkan dibagikan.

Secara umum, penggunaan tautan dinamis akan menghindarkan Anda dari keharusan memberikan apa pun.

Membangun aplikasi C/C++

Untuk membangun aplikasi C/C++ untuk platform dan distribusi yang berbeda, cukup dengan memilih atau membangun versi gcc yang sesuai dan menggunakan kompiler silang untuk arsitektur tertentu dan merakit seluruh rangkaian perpustakaan. Pekerjaan ini cukup layak dilakukan, namun cukup merepotkan. Dan tidak ada jaminan bahwa kompiler dan perpustakaan yang dipilih akan menyediakan versi yang bisa diterapkan.

Keuntungan yang jelas: infrastrukturnya sangat disederhanakan, karena seluruh proses pembangunan dapat diselesaikan pada satu mesin. Selain itu, cukup mengumpulkan satu set binari untuk satu arsitektur dan Anda dapat mengemasnya ke dalam paket untuk distribusi berbeda. Beginilah cara paket veeam dibuat untuk Veeam Agent untuk Linux.

Berbeda dengan opsi ini, Anda cukup menyiapkan build farm, yaitu beberapa mesin untuk perakitan. Setiap mesin tersebut akan menyediakan kompilasi aplikasi dan perakitan paket untuk distribusi tertentu dan arsitektur tertentu. Dalam hal ini kompilasi dilakukan dengan menggunakan sarana yang disiapkan oleh distributor. Artinya, tahap penyiapan compiler dan pemilihan pustaka dihilangkan. Selain itu, proses pembangunan dapat dengan mudah diparalelkan.

Namun, ada kelemahan dari pendekatan ini: untuk setiap distribusi dalam arsitektur yang sama, Anda harus mengumpulkan kumpulan file biner Anda sendiri. Kerugian lainnya adalah sejumlah besar mesin perlu dipelihara dan sejumlah besar ruang disk dan RAM harus dialokasikan.

Ini adalah bagaimana paket KMOD dari modul kernel veeamsnap dikompilasi untuk distribusi Red Hat.

Buka Layanan Bangun

Rekan-rekan dari SUSE mencoba menerapkan beberapa jalan tengah dalam bentuk layanan khusus untuk menyusun aplikasi dan merakit paket - layanan openbuild.

Pada dasarnya, ini adalah hypervisor yang membuat mesin virtual, menginstal semua paket yang diperlukan di dalamnya, mengkompilasi aplikasi dan membangun paket di lingkungan yang terisolasi ini, setelah itu mesin virtual dirilis.

Linux memiliki banyak wajah: cara bekerja pada distribusi apa pun

Penjadwal yang diterapkan di OpenBuildService akan menentukan berapa banyak mesin virtual yang dapat diluncurkan untuk kecepatan pembuatan paket yang optimal. Mekanisme penandatanganan bawaan akan menandatangani paket dan mengunggahnya ke repositori bawaan. Sistem kontrol versi bawaan akan menyimpan riwayat perubahan dan pembuatan. Yang tersisa hanyalah menambahkan sumber Anda ke sistem ini. Anda bahkan tidak perlu menyiapkan server sendiri; Anda dapat menggunakan server terbuka.

Namun terdapat permasalahan: alat pemanen seperti ini sulit untuk dipasang pada infrastruktur yang ada. Misalnya, kontrol versi tidak diperlukan; kami sudah memiliki kode sumbernya sendiri. Mekanisme tanda tangan kami berbeda: kami menggunakan server khusus. Repositori juga tidak diperlukan.

Selain itu, dukungan untuk distribusi lain - misalnya, Red Hat - diterapkan dengan agak buruk, dan hal ini dapat dimengerti.

Keuntungan dari layanan ini adalah dukungan cepat untuk distribusi SUSE versi berikutnya. Sebelum pengumuman resmi rilisnya, paket-paket yang diperlukan untuk perakitan diposting di repositori publik. Yang baru muncul di daftar distribusi yang tersedia di OpenBuildService. Kami mencentang kotak dan itu ditambahkan ke rencana pembangunan. Jadi, menambahkan versi baru dari distribusi dapat dilakukan dalam hampir satu klik.

Di infrastruktur kami, menggunakan OpenBuildService, seluruh variasi paket KMP dari modul kernel veeamsnap untuk distribusi SUSE dirakit.

Selanjutnya, saya ingin membahas masalah khusus modul kernel.

inti ABI

Modul kernel Linux secara historis didistribusikan dalam bentuk sumber. Faktanya adalah pembuat kernel tidak membebani dirinya sendiri dengan kepedulian untuk mendukung API yang stabil untuk modul kernel, dan terutama pada level biner, yang selanjutnya disebut sebagai kABI.

Untuk membangun modul untuk kernel vanilla, Anda pasti memerlukan header dari kernel khusus ini, dan itu hanya akan berfungsi pada kernel ini.

DKMS memungkinkan Anda mengotomatiskan proses pembuatan modul saat memperbarui kernel. Akibatnya, pengguna repositori Debian (dan banyak kerabatnya) menggunakan modul kernel baik dari repositori distributor atau dikompilasi dari sumber menggunakan DKMS.

Namun, situasi ini tidak terlalu cocok untuk segmen Enterprise. Distributor kode kepemilikan ingin mendistribusikan produk sebagai biner terkompilasi.

Administrator tidak ingin menyimpan alat pengembangan di server produksi karena alasan keamanan. Distributor Linux perusahaan seperti Red Hat dan SUSE memutuskan bahwa mereka dapat mendukung kABI yang stabil untuk penggunanya. Hasilnya adalah paket KMOD untuk Red Hat dan paket KMP untuk SUSE.

Inti dari solusi ini cukup sederhana. Untuk versi distribusi tertentu, API kernel dibekukan. Distributor menyatakan bahwa ia menggunakan kernel, misalnya 3.10, dan hanya melakukan koreksi dan peningkatan yang tidak mempengaruhi antarmuka kernel, dan modul yang dikumpulkan untuk kernel pertama dapat digunakan untuk semua kernel berikutnya tanpa kompilasi ulang.

Red Hat mengklaim kompatibilitas kABI untuk distribusi di seluruh siklus hidupnya. Artinya, modul rakitan untuk rhel 6.0 (rilis November 2010) juga harus berfungsi pada versi 6.10 (rilis Juni 2018). Dan ini hampir 8 tahun. Tentu saja, tugas ini cukup sulit.
Kami telah mencatat beberapa kasus dimana modul veeamsnap berhenti bekerja karena masalah kompatibilitas kABI.

Setelah modul veeamsnap, yang dikompilasi untuk RHEL 7.0, ternyata tidak kompatibel dengan kernel dari RHEL 7.5, tetapi dimuat dan dijamin akan membuat server crash, kami sama sekali mengabaikan penggunaan kompatibilitas kABI untuk RHEL 7.

Saat ini, paket KMOD untuk RHEL 7 berisi rakitan untuk setiap versi rilis dan skrip yang memuat modul.

SUSE melakukan tugas kompatibilitas kABI dengan lebih hati-hati. Mereka menyediakan kompatibilitas kABI hanya dalam satu paket layanan.

Misalnya saja perilisan SLES 12 terjadi pada bulan September 2014. Dan SLES 12 SP1 sudah pada bulan Desember 2015, yakni setahun lebih telah berlalu. Meskipun kedua rilis menggunakan kernel 3.12, keduanya tidak kompatibel dengan kABI. Tentu saja, menjaga kompatibilitas kABI selama satu tahun saja jauh lebih mudah. Siklus pembaruan modul kernel tahunan seharusnya tidak menimbulkan masalah bagi pembuat modul.

Sebagai akibat dari kebijakan SUSE ini, kami belum mencatat satu pun masalah kompatibilitas kABI di modul veeamsnap kami. Benar, jumlah paket untuk SUSE hampir jauh lebih besar.

Patch dan backport

Meskipun distributor mencoba memastikan kompatibilitas kABI dan stabilitas kernel, mereka juga mencoba meningkatkan kinerja dan menghilangkan cacat pada kernel stabil ini.

Pada saat yang sama, selain “mengatasi kesalahan” mereka sendiri, para pengembang kernel Linux perusahaan memantau perubahan dalam kernel vanilla dan mentransfernya ke kernel “stabil”.

Terkadang hal ini mengarah pada hal baru kesalahan.

Dalam rilis terbaru Red Hat 6, terjadi kesalahan di salah satu pembaruan kecil. Hal ini mengarah pada fakta bahwa modul veeamsnap dijamin akan membuat sistem crash ketika snapshot dirilis. Setelah membandingkan sumber kernel sebelum dan sesudah pembaruan, kami menemukan bahwa backport adalah penyebabnya. Perbaikan serupa dilakukan pada kernel vanilla versi 4.19. Hanya saja perbaikan ini berfungsi dengan baik di kernel vanilla, namun saat dipindahkan ke "stable" 2.6.32, muncul masalah pada spinlock.

Tentu saja, setiap orang selalu mengalami kesalahan, tetapi apakah layak menyeret kode dari 4.19 ke 2.6.32, mempertaruhkan stabilitas?.. Saya tidak yakin...

Hal terburuknya adalah ketika pemasaran terlibat dalam tarik-menarik antara “stabilitas” dan “modernisasi.” Departemen pemasaran membutuhkan inti dari distribusi yang diperbarui agar stabil, di satu sisi, dan pada saat yang sama memiliki kinerja yang lebih baik dan memiliki fitur-fitur baru. Hal ini mengarah pada kompromi yang aneh.

Ketika saya mencoba membangun modul pada kernel 4.4 dari SLES 12 SP3, saya terkejut menemukan fungsionalitas dari vanilla 4.8 di dalamnya. Menurut saya, implementasi blok I/O kernel 4.4 dari SLES 12 SP3 lebih mirip dengan kernel 4.8 dibandingkan rilis sebelumnya kernel 4.4 stabil dari SLES12 SP2. Saya tidak dapat menilai berapa persentase kode yang ditransfer dari kernel 4.8 ke SLES 4.4 untuk SP3, tetapi saya bahkan tidak dapat menyebut kernel tersebut sebagai stable 4.4 yang sama.

Hal yang paling tidak menyenangkan tentang hal ini adalah ketika menulis modul yang dapat bekerja dengan baik pada kernel yang berbeda, Anda tidak dapat lagi mengandalkan versi kernel. Anda juga harus memperhitungkan distribusinya. Ada baiknya terkadang Anda bisa terlibat dalam definisi yang muncul bersamaan dengan fungsi baru, tetapi peluang ini tidak selalu muncul.

Akibatnya, kode menjadi ditumbuhi arahan kompilasi bersyarat yang aneh.

Ada juga patch yang mengubah API kernel yang terdokumentasi.
Saya menemukan distribusinya Neon KDE 5.16 dan sangat terkejut melihat panggilan lookup_bdev di versi kernel ini mengubah daftar parameter input.

Untuk menyatukannya, saya harus menambahkan skrip ke makefile yang memeriksa apakah fungsi lookup_bdev memiliki parameter mask.

Menandatangani modul kernel

Tapi mari kita kembali ke masalah distribusi paket.

Salah satu kelebihan kABI stabil adalah modul kernel dapat ditandatangani sebagai file biner. Dalam hal ini, pengembang dapat yakin bahwa modul tersebut tidak rusak secara tidak sengaja atau dimodifikasi secara sengaja. Anda dapat memeriksanya dengan perintah modinfo.

Distribusi Red Hat dan SUSE memungkinkan Anda memeriksa tanda tangan modul dan memuatnya hanya jika sertifikat terkait terdaftar pada sistem. Sertifikat adalah kunci publik yang digunakan untuk menandatangani modul. Kami mendistribusikannya sebagai paket terpisah.

Masalahnya di sini adalah bahwa sertifikat dapat dimasukkan ke dalam kernel (distributor menggunakannya) atau harus ditulis ke memori non-volatile EFI menggunakan utilitas mokutil. Kegunaan mokutil Saat memasang sertifikat, Anda harus melakukan boot ulang sistem dan, bahkan sebelum memuat kernel sistem operasi, meminta administrator untuk mengizinkan pemuatan sertifikat baru.

Oleh karena itu, penambahan sertifikat memerlukan akses administrator fisik ke sistem. Jika mesin terletak di suatu tempat di cloud atau hanya di ruang server jarak jauh dan akses hanya melalui jaringan (misalnya, melalui ssh), maka tidak mungkin menambahkan sertifikat.

EFI pada mesin virtual

Terlepas dari kenyataan bahwa EFI telah lama didukung oleh hampir semua produsen motherboard, ketika menginstal suatu sistem, administrator mungkin tidak memikirkan perlunya EFI, dan mungkin dinonaktifkan.

Tidak semua hypervisor mendukung EFI. VMWare vSphere mendukung EFI mulai dari versi 5.
Microsoft Hyper-V juga mendapatkan dukungan EFI dimulai dengan Hyper-V untuk Windows Server 2012R2.

Namun, dalam konfigurasi default, fungsi ini dinonaktifkan untuk mesin Linux, yang berarti sertifikat tidak dapat diinstal.

Di vSphere 6.5, atur opsi Secure Boot hanya mungkin di antarmuka web versi lama, yang berjalan melalui Flash. Web UI pada HTML-5 masih tertinggal jauh.

Distribusi eksperimental

Dan terakhir, mari kita pertimbangkan masalah distribusi eksperimental dan distribusi tanpa dukungan resmi. Di satu sisi, distribusi seperti itu tidak mungkin ditemukan di server organisasi yang serius. Tidak ada dukungan resmi untuk distribusi tersebut. Oleh karena itu, sediakan itu. Produk tidak dapat didukung pada distribusi seperti itu.

Namun, distribusi tersebut menjadi platform yang nyaman untuk mencoba solusi eksperimental baru. Misalnya saja Fedora, OpenSUSE Tumbleweed, atau Debian versi tidak stabil. Mereka cukup stabil. Mereka selalu memiliki program versi baru dan selalu kernel baru. Dalam setahun, fungsi eksperimental ini mungkin akan tersedia di RHEL, SLES, atau Ubuntu yang diperbarui.

Jadi jika ada sesuatu yang tidak berfungsi pada distribusi eksperimental, ini adalah alasan untuk mencari tahu masalahnya dan menyelesaikannya. Anda harus bersiap dengan kenyataan bahwa fungsi ini akan segera muncul di server produksi pengguna.

Anda dapat mempelajari daftar distribusi resmi yang didukung untuk versi 3.0 di sini. Namun daftar distribusi sebenarnya di mana produk kami dapat bekerja jauh lebih luas.

Secara pribadi, saya tertarik dengan eksperimen dengan OS Elbrus. Setelah menyelesaikan paket veeam, produk kami telah diinstal dan berfungsi. Saya menulis tentang eksperimen ini di Habré pada tahun XNUMX Artikel.

Ya, dukungan untuk distribusi baru terus berlanjut. Kami menunggu versi 4.0 dirilis. Beta akan segera muncul, jadi waspadalah apa yang baru!

Sumber: www.habr.com

Tambah komentar