Собираем OpenConnect (ocserv) версии 1.3 из исходников в Debian 12 + docker образ

Собираем OpenConnect (ocserv) версии 1.3 из исходников в Debian 12 + docker образ

Приветствую!

По просьбе некоторых подписчиков из нашего вороньего чата, сегодня мы соберем OpenConnect VPN сервер, он же ocserv, последней версии – 1.3, из открытых исходных кодов, в дистрибутиве Debian 12. А также создадим Docker образ на основе того же дистрибутива.

Если вы не в курсе, что это за зверь такой, рекомендую мою другую статью к прочтению: Поднимаем OpenConnect SSL VPN сервер (ocserv) в docker для внутренних проектов

Все действия из данной статьи будут выполняться в следующей конфигурации:

Поехали 🙃

Сборка ocserv из исходных кодов

Первым делом, обновляем нашу текущую систему до последней версии такой командой:

sudo apt update && sudo apt upgrade -y

Далее подключаем репозиторий Debian Sid – репозиторий “нестабильных” пакетов дистрибутива, т.к. там находятся нужные нам версии библиотек. После, обязательно обновляем кэш для применения изменений:

echo "deb http://deb.debian.org/debian sid main" | sudo tee -a /etc/apt/sources.list

sudo apt update

Команда tee принимает на ввод данные и записывает их в файл, а также выводит в стандартный вывод (stdout). Ключ -a (append) означает добавить полученное содержимое в файл, а не перезаписать его полностью.

Далее нам необходимо установить большой список зависимостей 😳 Предусмотрите свободное пространство на вашем диске под эти цели.

sudo DEBIAN_FRONTEND=noninteractive apt install -y build-essential fakeroot devscripts \
        iputils-ping ruby-ronn openconnect libuid-wrapper \
        libnss-wrapper libsocket-wrapper gss-ntlmssp git-core make autoconf \
        libtool autopoint gettext automake nettle-dev libwrap0-dev \
        libpam0g-dev liblz4-dev libseccomp-dev libreadline-dev libnl-route-3-dev \
        libkrb5-dev liboath-dev libradcli-dev libprotobuf-dev libtalloc-dev \
        libhttp-parser-dev libpcl1-dev protobuf-c-compiler gperf liblockfile-bin \
        nuttcp libpam-oath libev-dev libgnutls28-dev gnutls-bin haproxy \
        yajl-tools libcurl4-gnutls-dev libcjose-dev libjansson-dev libssl-dev \
        iproute2 libpam-wrapper tcpdump libopenconnect-dev iperf3 ipcalc-ng \
        freeradius libfreeradius-dev

Переменная DEBIAN_FRONTEND=noninteractive указывает пакетному менеджеру apt выполнить установку исключая взаимодействие с пользователем. Т.е. при разрешении конфликтов во время установки, apt будет применять действие по умолчанию. Помните, что вы все делаете на свой риск и ответственность.

После установки всех нужных пакетов, скачиваем архив с исходниками ocserv с помощью утилиты curl. После чего распаковываем архив с помощью архиватора tar:

curl -fLO https://www.infradead.org/ocserv/download/ocserv-1.3.0.tar.xz

ls

tar -xvf ./ocserv-1.3.0.tar.xz

Используемые ключи tar:

  • -x – eXtract, собсно распаковка;
  • -vVerbose, подробный вывод процесса распаковки;
  • -fFile, после этого ключа указывается фал архива.

После распаковки переходим в созданную директорию с исходниками и запускаем сборку, а также проверку ocserv, и все это от имени root:

cd ./ocserv-1.3.0

sudo sh -c './configure --enable-oidc-auth && make && make check'

С помощью конструкции sh -c 'commands' мы объединили несколько команд в одну. Таким образом их проще запустить указав sudo один раз.

А параметр --enable-oidc-auth в команде configure указывает выполнить сборку с поддержкой OpenID Connect.

В итоге будет выполнен билд почти дефолтной конфигурации:

Процесс может занять длительное время, в зависимости от вашего железа и конфигурации компилятора.

И так сборка завершена, но.. видно 2 провальных теста:

  • haproxy-auth – в ходе изучения пришел к выводу, что проблема в обращении haproxy по адресу 127.0.0.2 во время теста, при сборке в виртуальной машине. Проблема известная и судя по всему ни на что не влияет. Подробнее в ишью в официальном GitLab и сам скрипт теста там же.
  • test-oidc – потому что требует OpenID auth токен для проверки.

Установка и запуск ocserv

Для установки собранных файлов в вашу систему, выполните:

sudo make install

Тут вы увидите, что и куда было установлено/скопировано.

Теперь проверяем:

whereis ocserv

sudo ocserv --version

Обратите внимание, что ocserv по умолчанию устанавливается в /usr/local/sbin.

Описывать, что нужно для запуска ocserv во всех подробностях не стану😐 Иначе статья получится слишком большая, да и цель ее не в этом. Для простоты установки, вы можете воспользоваться моим готовым bash скриптом, который я сделал для запуска ocserv в Docker из прошлой статьи.

Для этого нужно скачать его из моего GitHub и запустить следующим образом:

sudo mkdir /etc/ocserv

curl -fLO https://raw.githubusercontent.com/r4ven-me/openconnect/main/src/server/v1.3/ocserv.sh

chmod +x ./ocserv.sh

sudo ./ocserv.sh ocserv --foreground

При первом запуске скрипт сгенерит все нужные сертификаты с дефолтными значениями. При необходимости отредактируйте их вначале скрипта ocserv.sh.

Проверка в соседней вкладке:

ss -tulnap | grep 443

curl --insecure https://localhost:443

Утилита ss позволяет просматривать сетевую активность ОС. Параметр --insecure в curl позволяет выполнить запроса с игнорированием предупреждений о самоподписанных SSL сертификатах.

Отлично, все работает. Для выхода нажмите Ctrl+c на основной вкладке.

Сборка из исходных кодов внутри Docker

Теперь чутка поинтереснее 😌

Мои подписчики знают, что личные сервисы я предпочитаю разворачивать в Docker. Это сильно упрощает их обслуживание, масштабируемость и переносимость.

Поэтому сейчас, мы с вами выполним сборку ocserv 1.3 в одном в Docker контейнере с Debian 12, а в другой, такой же, установим парочку зависимостей и просто скопируем готовые исполняемые файлы из образа сборки, чтобы итоговый получился небольшого размера.

По сути, все тоже самое, что мы с вами проделали ранее, только внутри контейнеров без засорения основной системы.

Для сборки нам понадобится установленный Docker engine. Если это еще не сделано, рекомендую мою статью: Установка Docker engine на Linux сервер под управлением Debian.

Создание Docker образа для сборки ocserv 1.3

И так, создаем новую директорию и копируем туда Docker файл с описанием контейнера сборки из моего GitHub:

mkdir ~/ocserv && cd ~/ocserv/

curl -fLO https://raw.githubusercontent.com/r4ven-me/openconnect/main/src/server/v1.3/Dockerfile_build
Для просмотра содержимого Dockerfile_build клик сюда

FROM debian:12

LABEL maintainer="Ivan Cherniy <kar-kar@r4ven.me>"

ARG DEBIAN_FRONTEND=noninteractive

SHELL ["/bin/bash", "-Eeuo", "pipefail", "-c"]

RUN echo "deb http://deb.debian.org/debian sid main" >> /etc/apt/sources.list && \
    apt update && \
    apt upgrade -y && \
    apt install --yes curl build-essential fakeroot devscripts \
        iputils-ping ruby-ronn openconnect libuid-wrapper \
        libnss-wrapper libsocket-wrapper gss-ntlmssp git-core make autoconf \
        libtool autopoint gettext automake nettle-dev libwrap0-dev \
        libpam0g-dev liblz4-dev libseccomp-dev libreadline-dev libnl-route-3-dev \
        libkrb5-dev liboath-dev libradcli-dev libprotobuf-dev libtalloc-dev \
        libhttp-parser-dev libpcl1-dev protobuf-c-compiler gperf liblockfile-bin \
        nuttcp libpam-oath libev-dev libgnutls28-dev gnutls-bin haproxy \
        yajl-tools libcurl4-gnutls-dev libcjose-dev libjansson-dev libssl-dev \
        iproute2 libpam-wrapper tcpdump libopenconnect-dev iperf3 ipcalc-ng \
        freeradius libfreeradius-dev &&\
    curl -fLO https://www.infradead.org/ocserv/download/ocserv-1.3.0.tar.xz && \
    tar -xvf ./ocserv-1.3.0.tar.xz && \
    cd ./ocserv-1.3.0/ && \
    ./configure --enable-oidc-auth && make && \
    mkdir /usr/share/doc/ocserv/ && \
    cp ./doc/sample.config /usr/share/doc/ocserv/

WORKDIR /ocserv-1.3.0

CMD [ "sleep", "999999" ]

И запускаем сборку командой (займет некоторое время):

docker build -f Dockerfile_build ./ -t openconnect-build:v1.3

В этой команде с помощью ключа -f явно указывается файла описания сборки, ./ означает использовать текущую директорию как рабочую, а openconnect-build:v1.3 – это имя собираемого образа и его тег (v1.3), по которому мы будем обращаться при сборке конечного образа.

Готово. После завершения проверяем:

docker image ls

Целых 1.4 gb, толстоват😳 Но, как я говорил ранее, это образ сборки. Из него мы далее скопируем только скомпилированные файлы ocserv в итоговый образ.

Создание итогового Docker образа с ocserv 1.3

Вновь копируем из моего GitHub Docker файл с описанием конечного образа + уже ранее упомянутый bash скрипт для запуска ocserv:

curl -fLO https://raw.githubusercontent.com/r4ven-me/openconnect/main/src/server/v1.3/Dockerfile

curl -fLO https://raw.githubusercontent.com/r4ven-me/openconnect/main/src/server/v1.3/ocserv.sh

chmod +x ./ocserv.sh

ocserv.sh будет скопирован в образ во время сборки.

Для просмотра содержимого Dockerfile клик сюда

FROM openconnect-build:v1.3 AS builder

FROM debian:12

LABEL maintainer="Ivan Cherniy <kar-kar@r4ven.me>"

STOPSIGNAL SIGTERM

COPY --from=builder ["/ocserv-1.3.0/src/occtl/occtl", "/usr/bin"]
COPY --from=builder ["/ocserv-1.3.0/src/ocpasswd/ocpasswd", "/usr/bin"]
COPY --from=builder ["/ocserv-1.3.0/src/ocserv-fw", "/usr/libexec"]
COPY --from=builder ["/ocserv-1.3.0/src/ocserv", "/usr/sbin"]
COPY --from=builder ["/ocserv-1.3.0/src/ocserv-worker", "/usr/sbin"]
COPY --from=builder ["/usr/share/doc/ocserv", "/usr/share/doc/ocserv"]

ENV OCSERV_DIR="/etc/ocserv"
ENV CERTS_DIR="${OCSERV_DIR}/certs"
ENV SSL_DIR="${OCSERV_DIR}/ssl"
ENV SECRETS_DIR="${OCSERV_DIR}/secrets"
ENV PATH="${OCSERV_DIR}:${PATH}"

ARG DEBIAN_FRONTEND=noninteractive

RUN echo "deb http://deb.debian.org/debian sid main" >> /etc/apt/sources.list && \
    apt update && \
    apt install -y --no-install-recommends \
        libllhttp9.1 \ 
        libtalloc2 \
        libradcli4 \
        liboath0 \
        libpcl1 \
        libev4 \
        libprotobuf-c1 \
        libreadline8 \
        libnl-route-3-200 \
        libcurl3-gnutls \
        libcjose0 \
        less \
        gnutls-bin \
        iptables \
        iproute2 \
        iputils-ping && \
    mkdir /etc/ocserv/ && \
    apt autoremove --yes && \
    apt clean -y && \
    rm -rf /var/lib/{apt,dpkg,cache,log}/*

COPY ./ocserv.sh /

ENTRYPOINT ["/ocserv.sh"]

CMD ["ocserv", "--config", "/etc/ocserv/ocserv.conf", "--foreground"]

HEALTHCHECK --interval=5m --timeout=3s \
  CMD  pidof -q ocserv-main || exit 1

Для просмотра содержимого ocserv.sh клик сюда

#!/bin/bash

# Some protection
set -Eeuo pipefail

# Define default server vars if they are not set
SRV_CN="${SRV_CN:=example.com}" 
SRV_CA="${SRV_CA:=Example CA}"

# Ocserv vars (do not modify)
OCSERV_DIR="/etc/ocserv"
CERTS_DIR="${OCSERV_DIR}/certs"
SSL_DIR="${OCSERV_DIR}/ssl"
SECRETS_DIR="${OCSERV_DIR}/secrets"

# Start server if data files exist
if [[ -r "${OCSERV_DIR}"/ocserv.conf ]]; then
    echo "Starting OpenConnect Server"
    exec "$@" || { echo "Starting failed" >&2; exit 1; }
else
    echo "Running OpenConnect Server at first with new certs generation"
fi

# Create certs dirs
if [[ -d $OCSERV_DIR ]]; then
    for sub_dir in "${OCSERV_DIR}"/{"ssl/live/${SRV_CN}","certs","secrets"}; do
        mkdir -p "$sub_dir"
    done
    if [[ -r /usr/share/doc/ocserv/sample.config ]]; then
        cp /usr/share/doc/ocserv/sample.config "${OCSERV_DIR}"/
    fi
fi

# Create ocserv config file
cat << _EOF_ > "${OCSERV_DIR}"/ocserv.conf
auth = "certificate"
#auth = "plain[passwd=${OCSERV_DIR}/ocpasswd]"
#enable-auth = "certificate"
tcp-port = 443
socket-file = /run/ocserv-socket
server-cert = ${SSL_DIR}/live/${SRV_CN}/fullchain.pem
server-key = ${SSL_DIR}/live/${SRV_CN}/privkey.pem
ca-cert = ${CERTS_DIR}/ca-cert.pem
isolate-workers = true
max-clients = 20
max-same-clients = 2
rate-limit-ms = 200
server-stats-reset-time = 604800
keepalive = 10
dpd = 120
mobile-dpd = 1800
switch-to-tcp-timeout = 25
try-mtu-discovery = false
cert-user-oid = 0.9.2342.19200300.100.1.1
tls-priorities = "NORMAL:%SERVER_PRECEDENCE:%COMPAT:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1:-VERS-TLS1.3"
auth-timeout = 1000
min-reauth-time = 300
max-ban-score = 100
ban-reset-time = 1200
cookie-timeout = 600
deny-roaming = false
rekey-time = 172800
rekey-method = ssl
connect-script = ${OCSERV_DIR}/connect.sh
disconnect-script = ${OCSERV_DIR}/disconnect.sh
use-occtl = true
pid-file = /run/ocserv.pid
log-level = 1
device = vpns
predictable-ips = true
default-domain = $SRV_CN
ipv4-network = 10.10.10.0
ipv4-netmask = 255.255.255.0
tunnel-all-dns = true
dns = 8.8.8.8
ping-leases = false
config-per-user = ${OCSERV_DIR}/config-per-user/
cisco-client-compat = true
dtls-legacy = true
client-bypass-protocol = false
crl = /etc/ocserv/certs/crl.pem
_EOF_

# Create template for CA SSL cert
cat << _EOF_ > "${CERTS_DIR}"/ca.tmpl
organization = $SRV_CN
cn = $SRV_CA
serial = 001
expiration_days = -1
ca
signing_key
cert_signing_key
crl_signing_key
_EOF_

# Create template for users SSL certs
cat << _EOF_ > "${CERTS_DIR}"/users.cfg
organization = $SRV_CN
cn = Example User
uid = exampleuser
expiration_days = -1
tls_www_client
signing_key
encryption_key
_EOF_

# Create template for server self-signed SSL cert
cat << _EOF_ > "${SSL_DIR}"/server.tmpl
cn = $SRV_CA
dns_name = $SRV_CN
organization = $SRV_CN
expiration_days = -1
signing_key
encryption_key #only if the generated key is an RSA one
tls_www_server
_EOF_

# Generate empty revoke file
cat << _EOF_ > "${CERTS_DIR}"/crl.tmpl
crl_next_update = 365
crl_number = 1
_EOF_

# Create connect script which runs for every user connection
cat << _EOF_ > "${OCSERV_DIR}"/connect.sh && chmod +x "${OCSERV_DIR}"/connect.sh
#!/bin/bash

set -Eeuo pipefail

echo "\$(date) User \${USERNAME} Connected - Server: \${IP_REAL_LOCAL} VPN IP: \${IP_REMOTE}  Remote IP: \${IP_REAL} Device:\${DEVICE}"
echo "Running iptables MASQUERADE for User \${USERNAME} connected with VPN IP \${IP_REMOTE}"
iptables -t nat  -A POSTROUTING -s \${IP_REMOTE}/32 -o eth0 -j MASQUERADE
_EOF_

# Create disconnect script which runs for every user disconnection
cat << _EOF_ > "${OCSERV_DIR}"/disconnect.sh && chmod +x "${OCSERV_DIR}"/disconnect.sh
#!/bin/bash

set -Eeuo pipefail

echo "\$(date) User \${USERNAME} Disconnected - Bytes In: \${STATS_BYTES_IN} Bytes Out: \${STATS_BYTES_OUT} Duration:\${STATS_DURATION}"
_EOF_

# Create script to create new users
cat << _EOF_ > "${OCSERV_DIR}"/ocuser && chmod +x "${OCSERV_DIR}"/ocuser
#!/bin/bash

set -Eeuo pipefail

# Check and set script params
if [[ \$# -eq 2 ]]; then
    USER_UID="\$1"
    USER_CN="\$2"
elif [[ \$# -eq 3 ]]; then
	if [[ "\$1" == "-A" ]]; then
    		USER_UID="\$2"
    		USER_CN="\$3"
	else
		echo "Use -A key as a first param to generate cert for IOS devices" >&2
        exit 1
	fi
else
    echo "Please run script with two params: username and 'Common Username'" >&2
    echo "Example: ocuser john 'John Doe'" >&2
    echo "For IOS or HarmonyOS devices add -A key as first param in command" >&2
    echo "Example: ocuser -A steve 'Steve Jobs'" >&2
    exit 1
fi

# Modify user cert template and generate user key, cert and protected .p12 file
sed -i -e "s/^organization.*/organization = \$SRV_CN/" -e "s/^cn.*/cn = \$USER_CN/" -e "s/^uid.*/uid = \$USER_UID/g" "\${CERTS_DIR}"/users.cfg
echo "\$(tr -cd "[:alnum:]" < /dev/urandom | head -c 60)" | ocpasswd -c "\${OCSERV_DIR}"/ocpasswd "\$USER_UID"
certtool --generate-privkey --outfile "\${CERTS_DIR}"/"\${USER_UID}"-privkey.pem
certtool --generate-certificate --load-privkey "\${CERTS_DIR}"/"\${USER_UID}"-privkey.pem --load-ca-certificate "\${CERTS_DIR}"/ca-cert.pem --load-ca-privkey "\${CERTS_DIR}"/ca-key.pem --template "\${CERTS_DIR}"/users.cfg --outfile "\${CERTS_DIR}"/"\${USER_UID}"-cert.pem
if [[ "\$1" == "-A" ]]; then
	sleep 1 && certtool --to-p12 --load-certificate "\${CERTS_DIR}"/"\${USER_UID}"-cert.pem --load-privkey "\${CERTS_DIR}"/"\${USER_UID}"-privkey.pem --pkcs-cipher 3des-pkcs12 --hash SHA1 --outder --outfile "\${SECRETS_DIR}"/"\${USER_UID}".p12
else
	sleep 1 && certtool --load-certificate "\${CERTS_DIR}"/"\${USER_UID}"-cert.pem --load-privkey "\${CERTS_DIR}"/"\${USER_UID}"-privkey.pem --pkcs-cipher aes-256 --to-p12 --outder --outfile "\${SECRETS_DIR}"/"\${USER_UID}".p12
fi
_EOF_

# Add revoke script
cat << _EOF_ > "${OCSERV_DIR}"/ocrevoke && chmod +x "${OCSERV_DIR}"/ocrevoke
#!/bin/bash

set -Eeuo pipefail

if [[ ! -e "\${CERTS_DIR}"/crl.tmpl ]]; then
cat << __EOF__ > "\${CERTS_DIR}"/crl.tmpl
crl_next_update = 365
crl_number = 1
__EOF__
fi

if [[ \$# -eq 1 ]]; then
    if [[ "\$1" == "HELP" ]]; then
        echo "Usage:
        CMD to revoke cert of some user: ocrevoke <exist_user> 
        CMD to apply current revoked.pem: ocrevoke RELOAD
        CMD to reset all revokes: ocrevoke RESET
        CMD to print this help: ocrevoke HELP"
    elif [[ "\$1" == "RESET" ]]; then
        certtool --generate-crl --load-ca-privkey "\${CERTS_DIR}"/ca-key.pem --load-ca-certificate "\${CERTS_DIR}"/ca-cert.pem --template "\${CERTS_DIR}"/crl.tmpl --outfile "\${CERTS_DIR}"/crl.pem
        occtl reload
    elif [[ "\$1" == "RELOAD" ]]; then
        certtool --generate-crl --load-ca-privkey "\${CERTS_DIR}"/ca-key.pem --load-ca-certificate "\${CERTS_DIR}"/ca-cert.pem --load-certificate "\${CERTS_DIR}"/revoked.pem --template "\${CERTS_DIR}"/crl.tmpl --outfile "\${CERTS_DIR}"/crl.pem
    else
        USER_UID="\$1"
        cat "\${CERTS_DIR}"/"\${USER_UID}"-cert.pem >> "\${CERTS_DIR}"/revoked.pem
        certtool --generate-crl --load-ca-privkey "\${CERTS_DIR}"/ca-key.pem --load-ca-certificate "\${CERTS_DIR}"/ca-cert.pem --load-certificate "\${CERTS_DIR}"/revoked.pem --template "\${CERTS_DIR}"/crl.tmpl --outfile "\${CERTS_DIR}"/crl.pem
        occtl reload
    fi
else
    echo "Usage:
    CMD to revoke cert of some user: ocrevoke <exist_user> 
    CMD to apply current revoked.pem: ocrevoke RELOAD
    CMD to reset all revokes: ocrevoke RESET
    CMD to print this help: ocrevoke HELP"
fi
_EOF_

# Server certificates generation
certtool --generate-privkey --outfile "${CERTS_DIR}"/ca-key.pem
certtool --generate-self-signed --load-privkey "${CERTS_DIR}"/ca-key.pem --template "${CERTS_DIR}"/ca.tmpl --outfile "${CERTS_DIR}"/ca-cert.pem
certtool --generate-crl --load-ca-privkey "${CERTS_DIR}"/ca-key.pem --load-ca-certificate "${CERTS_DIR}"/ca-cert.pem --template "${CERTS_DIR}"/crl.tmpl --outfile "${CERTS_DIR}"/crl.pem
if [[ ! -e  "${SSL_DIR}"/live/"${SRV_CN}"/privkey.pem && ! -e "${SSL_DIR}"/live/"${SRV_CN}"/fullchain.pem ]]; then
    certtool --generate-privkey --outfile "${SSL_DIR}"/live/"${SRV_CN}"/privkey.pem
    certtool --generate-certificate --load-privkey "${SSL_DIR}"/live/"${SRV_CN}"/privkey.pem --load-ca-certificate "${CERTS_DIR}"/ca-cert.pem --load-ca-privkey "${CERTS_DIR}"/ca-key.pem --template "${SSL_DIR}"/server.tmpl --outfile "${SSL_DIR}"/live/"${SRV_CN}"/fullchain.pem
fi

# Start ocserv service
echo "Starting OpenConnect Server"
exec "$@" || { echo "Starting failed" >&2; exit 1; }

И запускаем сборку командой:

docker build -f Dockerfile ./ -t openconnect:v1.3

Обратите внимание, что имя файла сборки и образа с тегом другие.

Готово.

Смотрим список образов:

docker image ls

Разница очевидна.

Запуск ocserv в Docker контейнере

Тестовый запуск можно провести так:

docker run --rm --detach openconnect:v1.3

docker ps

Но рекомендую воспользоваться docker compose:

curl -fLO https://raw.githubusercontent.com/r4ven-me/openconnect/main/src/server/v1.3/docker-compose.yml

curl -fLO https://raw.githubusercontent.com/r4ven-me/openconnect/main/src/server/v1.3/.env

Заполните файл .env при необходимости и запустите:

docker compose up -d

docker compose ps

А еще лучше изучите ранее упоминаемую статью: Поднимаем OpenConnect SSL VPN сервер (ocserv) в docker для внутренних проектов.

Заключение

Мне кажется, получился довольно наглядный кейс, как выглядит сборка ПО из исходных кодов. Отмечу, что многое в такой ситуации зависит от документации, которую пишут разработчики. В случае сборки ocserv документация неплоха, но далеко не идеально. Некоторые проблемы заняли у меня довольно много времени. Но с другой стороны, это открытое ПО, и чаще всего, распространяется оно по лицензиям, которые не подразумевают какую-либо гарантию. Это когда ПО, что называется, “поставляется как есть”.

В любом случае для меня это был интересный и безусловно полезный опыт.

Если у вас остались вопросы или есть что обсудить, заглядывайте в наш чат телеги: @r4ven_me_chat. Ну и конечно подписывайтесь на основной канал там же: @r4ven_me – ссылки на все новые посты приходят туда в день публикации. А также там проводятся Linux викторины😉

Спасибо, что читаете. Желаю вам безошибочных сборок из исходников😎

Используемые материалы

Подписаться
Уведомить о
0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии