воскресенье, 25 августа 2019 г.

IPv6 от Дом.ру (ЭР-Телеком) в Debian Stretch

Отключение IPv6

До сих пор мне приходилось лишь отключать поддержку IPv6 на компьютерах, т.к. она скорее создавала дополнительные риски для безопасности системы, чем приносила какую-то практическую пользу. Напомню, что для отключения IPv6 на уровне операционной системы нужно выполнить команду:
# sysctl -w net.ipv6.conf.all.disable_ipv6=1
Чтобы поддержка IPv6 не включалась при перезагрузке системы, нужно прописать в файл /etc/sysctl.conf такую строчку:
net.ipv6.conf.all.disable_ipv6=1
При этом интерфейсам не будут присваиваться адреса IPv6, однако различные сетевые демоны по-прежнему будут готовы обслужить запрос, поступивший через стек IPv6. Чтобы полностью отключить весь стек IPv6, нужно в файле /etc/default/grub добавить в переменную GRUB_CMDLINE_LINUX опцию, которая сообщит ядру о необходимости отключить весь стек IPv6:
GRUB_CMDLINE_LINUX="ipv6.disable=1"
После этого нужно обновить файлы конфигурации загрузчика GRUB при помощи следующей команды:
# update-grub
И перезагрузить систему.

Однако, недавно в личном кабинете Дом.ru моё внимание привлёк переключатель поддержки IPv6. Нельзя сказать, что в IPv6 и сейчас есть много пользы, однако поддержка IPv6 со стороны провайдера побудила меня изучить вопрос. Я достал с полки когда-то купленную книжку IPv6. Администрирование сетей и стал почитывать вечерами.

Надо сказать, что принципы организации IPv6 значительно отличаются от принципов организации ныне используемого протокола IPv4. Это не просто новая версия IPv4 с увеличенной длинной поля IP-адреса. Разработчики протокола подошли к задаче основательно, пытаясь решить множество проблем, накопившихся в IPv4. На мой взгляд, IPv6 напоминает чем-то гибрид IPv4 и IPX.

Организация сетей IPv6 с точки зрения магистрального сетевого администратора

С точки зрения сетевого администратора, работающего в компании-провайдере, IPv6 в целом отличается от IPv4 довольно мало. Провайдер обращается к региональному регистратору для получения блока IPv6-адресов. Регистраторы выдают провайдерам блоки с префиксами /32 из адресного пространства 2000::/3 - это «белые» IPv6-адреса.

Следующие 32 бита - это номера сетей внутри блока. Провайдерам настоятельно рекомендуется анонсировать по протоколу BGP префиксы не длиннее /48, чтобы глобальные таблицы маршрутов не разрослись сверх меры. Клиентам рекомендуется выдавать префиксы сетей с маской /56. Это значит, что из 32 бит 24 бита остаются в распоряжении провайдера, а следующие 8 бит клиент может использовать по своему усмотрению. То есть провайдер может раздать по 256 сетей каждому из максимум 16 миллионов (2^24) клиентов, имея всего один блок /32. При этом у клиента остаётся возможность организовать адресное пространство таким образом, чтобы выделить каждому из филиалов по несколько сетей для различных нужд. Например, 16 филиалам по 16 сетей, или 64 филиалам по 4 сети.

Наконец, оставшиеся 64 бита используются в качестве идентификатора интерфейса сетевого устройства внутри одной сети. Одна сеть может содержать столько устройств, сколько необходимо. Для формирования идентификатора интерфейса используются идентификаторы EUI-64. Идентификатор EUI-64 получается из MAC-адреса следующим образом:
  1. у первого октета MAC-адреса выставляется в единицу бит 1 (выполняется операция «побитовое или» со значением 0x02),
  2. между первыми и последними тремя октетами вставляется два октета - ff fe.

Организация сетей IPv6 с точки зрения сетевого администратора

Для сетевого администратора есть несколько интересных отличий IPv6 от IPv4.

Во-первых, в IPv6 встроены функции автоконфигурации сетевых интерфейсов. Каждый интерфейс назначает себе IPv6-адрес с префиксом fe80::/10, который не подлежит маршрутизации. Эти автоконфигурируемые IPv6-адреса используются для общения с маршрутизаторами и соседями по сети. Благодаря этому минимальная связность сети обеспечивается автоматически, а протоколы конфигурирования значительно упрощаются.

Во-вторых, т.к. автоконфигурирование обеспечивает минимальную связность сети, упрощаются протоколы обнаружения соседей и более полного автоконфигурирования. В IPv6 функции ARP и частично DHCP включены в протокол ICMPv6. Вместо ARP в ICMPv6 встроен протокол обнаружения соседей NDP - Neighbor Discovery Protocol. Вместо DHCP для получения маршрутов используется функция ICMPv6 под названием RA - Router Advertisement. Для получения адресов DNS-серверов и доменного суффикса по умолчанию в ICMPv6 существуют аналогичные функции RDNSS - Recursive DNS Servers и DNSSL - DNS Search List. Для более сложных случаев можно использовать DHCPv6, роль которого в IPv6 значительно снизилась - он используется для получения узлами различных настроек, таких как списки DNS или NTP-серверов, но не используется для конфигурирования адресов интерфейсов или раздачи информации о маршрутах.

В-третьих, IPv6 не использует широковещательные рассылки, а усиленно использует мультикаст-адресацию. Например, в протоколе NDP, чтобы узнать MAC-адреса устройства в сети, запрос отправляется на мультикаст-адрес, сформированный из части IPv6-адреса искомого устройства. Т.к. коммутаторам необходимо помнить, какие из портов относятся к каждому мультикаст-адресу, увеличиваются требования к максимальному объёму таблицы коммутации, но при этом снижается паразитная нагрузка на каналы связи, коммутаторы и конечные узлы, возникавшая из-за необходимости рассылать широковещательные пакеты. В IPv6 отпадает всякий смысл в делении крупных сетей на мелкие широковещательные домены. Остаётся осмысленным только выделение сети по организационным соображениям или из соображений безопасности. Например, можно выделять по отдельной сети для филиалов компании, или по сети для VoIP-шлюзов в филиале, с целью ограничить доступ к сети VoIP-шлюзов на маршрутизаторах, вне зависимости от количества оборудования в этой сети. В одной сети, благодаря использованию мультикаст-рассылок, могут уживаться хоть миллионы устройств - лишь бы коммутаторам хватало объёма таблицы коммутации.

Теперь можно перейти к практике.

Настройка одного интерфейса с "белым" адресом IPv6

Перед тем, как настраивать IPv6, сначала нужно настроить PPPoE-подключение с использованием IPv4. Сделать это можно одной из моих прошлых статей: Настройка PPPoE-подключения к ЭР-Телеком/Дом.ru (Уфанет, Башинформсвязь).

Добавим в файл /etc/ppp/peers/ertelecom одну дополнительную опцию:
+ipv6
Можно попробовать переустановить PPPoE-подключение:
# ifdown ertelecom
# ifup ertelecom
После переустановки подключения на интерфейсе ppp0 можно будет увидеть IPv6-адрес. Посмотрим его при помощи такой команды:
# ip addr show ppp0
Однако, этот IPv6-адрес начинается с префикса fe80::/10, который аналогичен IPv4-адресам из сети 169.254.0.0/16. Это самоконфигурируемые IPv6-адреса, которые действуют только в пределах одного сегмента сети и не маршрутизируются в другие сегменты. В случае с Ethernet-интерфейсами самоконфигурируемый адрес назначается следующим образом:
  1. в качестве префикса выбирается префикс fe80::/10,
  2. в качестве идентификатора интерфейса используется идентификатор EUI-64, полученный из MAC-адреса интерфейса.
Идентификатор EUI-64 получается из MAC-адреса следующим образом:
  1. у первого октета MAC-адреса выставляется в единицу бит 1 (выполняется операция «побитовое или» со значением 0x02),
  2. между первыми и последними тремя октетами вставляется два октета - ff fe.
В случае с PPP-подключениями не существует MAC-адреса, из которого можно было бы сгенерировать младшие 64 бита адреса, поэтому стороны генерируют случайные идентификаторы и согласовывают их друг с другом: соглашаются на предложенные идентификаторы или отказываются, предлагая другие. Именно такие адреса и были настроены на интерфейсе ppp0.

Для получения «белого» префикса потребуется установить и настроить клиент DHCPv6, который запросит через интерфейс ppp0 префикс IPv6 и настроит его на Ethernet-интерфейсе. Я пробовал сначала воспользоваться демоном dhcpcd5, однако он не желал настраивать Ethernet-интерфейсы, для которых в файле /etc/network/interfaces не указан метод настройки dhcp:
Sep 11 22:39:19 stupin systemd[1]: Starting LSB: IPv4 DHCP client with IPv4LL support...
Sep 11 22:39:19 stupin dhcpcd[30762]: Not running dhcpcd because /etc/network/interfaces ... failed!
Sep 11 22:39:19 stupin dhcpcd[30762]: defines some interfaces that will use a DHCP client ... failed!
Sep 11 22:39:19 stupin systemd[1]: Started LSB: IPv4 DHCP client with IPv4LL support.
Я попытался включить настройку интерфейса по DHCPv6, вписав в файл /etc/network/interfaces следующие настройки:
iface eth0 inet6 dhcp
  request_prefix 1
Однако даже с такими настройками dhcpcd5 запускаться отказывался. Поэтому я решил попробовать другой DHCP-клиент, которым оказался клиент из проекта WIDE-DHCPv6.

Установим DHCPv6-клиент:
# apt-get install wide-dhcpv6-client
Пропишем настройки DHCPv6-клиента в файле /etc/wide-dhcpv6/dhcp6c.conf:
interface ppp0 {
  send ia-pd 0;
};

id-assoc pd 0 {
  prefix-interface eth0 {
    sla-len 0;
    ifid 1;
  };
};
Пропишем в файле /etc/default/wide-dhcpv6-client список интерфейсов, через которые DHCPv6-клиент должен запрашивать настройки:
INTERFACES="ppp0"
Включим автозапуск wide-dhcp6-client при загрузке системы и перезапустим его, чтобы новые настройки вступили в силу:
# systemctl enable wide-dhcpv6-client
# systemctl restart wide-dhcpv6-client
Теперь DHCPv6-клиент получает через интерфейс ppp0 префикс вида 2a02:2698:882X:XXXX::/64 и настраивает на интерфейсе eth0 «белый» IPv6-адрес с использованием этого префикса. Что характерно, выдаваемый IPv6-префикс - динамический, т.к. при каждом переподключении выдаётся другой.

В рецепте настройки DHCPv6-клиента написано, что для корректного удаления IPv6-адреса с Ethernet-интерфейса нужно соорудить дополнительные «костыли» в файлах /etc/ppp/ipv6-up.d/20-wide-client-start и /etc/ppp/ipv6-down.d/20-wide-client-stop. Я, однако, «костыли» немного преобразовал, чтобы при отключении PPPoE останавливался только тот экземпляр клиента DHCPv6, который получает настройки через подключение PPPoE.

Также при установке подключения нужно присвоить sysctl-переменной net.ipv6.conf.ppp0.accept_ra значение 2, чтобы система добавляла в таблицу маршрутизации IPv6-маршруты, анонсированные провайдером через интерфейс PPPoE. Присваивать это значение нужно именно через эти скрипты, т.к. интерфейс ppp0 появляется только после установки подключения и по умолчанию значение sysctl-переменной net.ipv6.conf.ppp0.accept_ra равно единице - система принимает анонсы маршрутов, но не добавляет их в таблицу маршрутизации.

В результате мои «костыли» приняли несколько другой вид. Содержимое файла /etc/ppp/ipv6-up.d/route:
#!/bin/sh

case "$PPP_IPPARAM" in
        ertelecom)

        sysctl -w net.ipv6.conf.$PPP_IFACE.accept_ra=2
        dhcp6ctl -C start interface $PPP_IFACE
        ;;

        *)
        echo "No PPP_IPPARAM defined"
        ;;
esac
Содержимое файла /etc/ppp/ipv6-down.d/route:
#!/bin/sh

case "$PPP_IPPARAM" in
        ertelecom)

        dhcp6ctl -C stop interface $PPP_IFACE
        ;;

        *)
        echo "No PPP_IPPARAM defined"
        ;;
esac
На обоих файлах нужно выставить бит исполнимости:
# chmod +x /etc/ppp/ipv6-up.d/route
# chmod +x /etc/ppp/ipv6-down.d/route

Настройка фильтрации пакетов IPv6

Хотя пока мало кто пользуется IPv6, всё же не стоит выставлять свой компьютер в «дикий» интернет, не прикрыв его пакетным фильтром. Базовая настройка пакетного фильтра такова:
# ip6tables -A INPUT -i lo -j ACCEPT
# ip6tables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# ip6tables -A INPUT -p icmpv6 -m icmpv6 ! --icmpv6-type redirect -j ACCEPT
# ip6tables -A INPUT -i ppp0 -p udp -m udp -s fe80::/10 --sport 547 --dport 546  -j ACCEPT
# ip6tables -A INPUT DROP
В настроенных выше правилах пакетного фильтра разрешается:
  1. любой трафик через локальный петлевой интерфейсе lo,
  2. любой входящий ICMPv6-трафик, кроме переадресаций,
  3. разрешается принимать настройки по протоколу DHCPv6 через интерфейсе ppp0.
Сохраним настроенные правила фильтрации пакетов IPv6 в файл /etc/network/ip6tables:
# ip6tables-save > /etc/network/ip6tables
И пропишем их автоматическую загрузку в файл /etc/network/interfaces при поднятии локального петлевого интерфейса:
auto lo
iface lo inet loopback
  pre-up iptables-restore < /etc/network/iptables
  pre-up ip6tables-restore < /etc/network/ip6tables

Настройка BIND и openresolv для работы с IPv6

В одной из моих прошлых заметок описана Настройка кэширующего DNS-сервера BIND в связке с openresolv. Т.к. я продолжаю использовать эту связку, мне нужно приспособить её для работы с IPv6.

Первым делом нужно включить обслуживание запросов на IPv6-адресах в bind9. Для этого открываем файл /etc/bind/named.conf.options и вписываем опцию для прослушивания всех IPv6-адресов компьютера:
listen-on-v6 { any; };
Чтобы bind9 отвечал на запросы с IPv6-адреса ::1 - аналога IPv4-адреса 127.0.0.1, нужно добавить этот адрес в список адресов, которые могут выполнять рекурсивные запросы. В моём случае список адресов, которым разрешается выполнять рекурсивные запросы к DNS-серверу, выглядит следующим образом:
allow-recursion { 127.0.0.1; 169.254.253.0/24; 169.254.254.0/24; ::1; };
Осталось перезапустить bind9, чтобы он начал принимать запросы на IPv6-адресах:
# systemctl restart bind9
Теперь нужно настроить openresolv так, чтобы он прописывал в файл /etc/resolv.conf IPv6-адрес DNS-сервера - ::1. Для этого открываем на редактирование файл /etc/resolvconf.conf и приводим опцию name_servers к следующему виду:
name_servers="127.0.0.1 ::1"
Осталось обновить файл /etc/resolv.conf в соответствии с новыми настройками openresolv. Сделать это можно при помощи следующей команды:
# resolvconf -u

Проверка правильности настройки IPv6

Проверим правильность настройки IPv6, открыв веб-страницу для тестирования IPv6 - Проверьте ваше IPv6 подключение. Результат проверки в моём случае выглядел следующим образом:

Ещё один тест IPv6, более подробный и требовательный - IPv6 test - IPv6/4 connectivity and speed test. Результат этой проверки оказался следующим:

К сожалению, настроить PTR-запись для IPv6-адреса может только провайдер, но нельзя настроить PTR-запись для IPv6-адреса, префикс которого провайдер выдаёт каждый раз разный. Я задавал вопрос в техподдержку ЭР-Телекома о том, возможно ли выдавать один и тот же «статический» префикс IPv6 подобно тому, как это организовано в случае «статического» адреса IPv4. Мне ответили, что такой возможности в настоящее время у них нет. Поэтому в этом тесте мне удалось добиться максимально возможного количества баллов - 19 из 20. Для получения 20 баллов уже потребуются телодвижения со стороны провайдера.

Раздача "белых" IPv6-адресов устройствам в локальной сети

Провайдер выдал нам «белый» префикс IPv6 с маской /64, а это значит, что у нас имеется целая сеть, IPv6-адреса из которой мы можем раздавать своим устройствам. Даже самое крупное предприятие с сотнями тысяч компьютеров может уместиться в одной такой сети, выдав каждому сетевому узлу по одному адресу IPv6. Нет никакой необходимости использовать NAT, а задача защиты устройств от «дикого» интернета решается фильтрацией пакетов. У крупных предприятий может возникнуть потребность в получении от провайдера более коротких префиксов лишь при необходимости логически организовать свою сеть: выделить отдельные сети своим подразделениям (например, филиалам в разных городах), а в подразделениях - выделить отдельные подсети для разных целей (например, отдельная сеть для компьютеров сотрудников, отдельная - для голосовых шлюзов и т.д).

Я лично столкнулся с необходимостью иметь хотя бы две сети. Одну из них я хотел бы использовать на Ethernet-интерфейсе, а другую - на WiFi-интерфейсе. Вообще, RFC6177 рекомендует выдавать клиентам префиксы длиной от /48 до /56, но провайдер не следует рекомендациям, жадничает и выдаёт префиксы /64. Сначала я попробовал запросить у провайдера сразу два префикса, изменив файл конфигурации /etc/wide-dhcpv6-client/dhcp6c.conf следующим образом:
interface ppp0 {
  send ia-pd 0;
  send ia-pd 1;
};

id-assoc pd 0 {
  prefix-interface eth0 {
    sla-len 0;
    ifid 1;
  };
};

id-assoc pd 1 {
  prefix-interface wlan0 {
    sla-len 0;
    ifid 1;
  };
};
Но в ответ на такой трюк DHCPv6-сервер провайдера просто перестал отвечать на мои запросы. Пробовал воспользоваться NDP-прокси ndppd - аналогом ARP-прокси для IPv6 и демоном ndprbrd. Вместе они должны были перехватывать NDP-запросы, транслировать их на другой интерфейс, а при получении ответа добавлять маршрут к обнаруженному соседу через конкретный интерфейс. Но по не понятным мне причинам этот вариант тоже не взлетел. Остаётся только ждать и надеяться, что в ЭР-Телекоме когда-нибудь реализуют делегирование префиксов короче /64. Даже /60, позволяющей выделить 16 сетей /64 мне для дома хватило бы с головой.

Поскольку на Ethernet-интерфейсе у меня нет оборудования, имеющего полноценную поддержку IPv6, но есть нотубук с Debian, который может подключаться к WiFi-сети, то я решил повесить эту сеть на интерфейс wlan0.

Т.к. на моём компьютере установлен DNS-сервер, я решил в анонсах RA раздавать его адрес. Правда, поскольку выдаваемый провайдером IPv6-префикс - динамический, придётся воспользоваться локальными самосконфигурированными на интерфейсах IPv6-адресами. Первым делом разрешим DNS-серверу обслуживать запросы на локальных адресах. Для этого откроем в текстовом редакторе файл /etc/bind/named.conf.options и отредактируем список адресов, для которых будут обслуживаться рекурсивные запросы. В этот список я добавил сеть fe08::/10:
allow-recursion { 127.0.0.1; 169.254.253.0/24; 169.254.254.0/24; ::1; fe08::/10; };
Это не очень хорошо, т.к. провайдер тоже сможет через PPPoE-подключение обращаться с рекурсивными запросами к моему DNS-серверу. Педант в моей душе немного негодует, однако сумел договориться с практиком, который не нашёл других выходов из этой ситуации. Сообщим DNS-серверу об изменившихся настройках:
# systemctl reload bind9
Для того, чтобы раздавать IPv6-адреса из выделенной нам сети, установим и настроим демона radvd - Router ADVertisement Daemon - демона объявления маршрутизатора. Установим одноимённый пакет:
# apt-get install radvd
Создадим файл /etc/radvd.conf со следующим содержимым:
interface wlan0
{
  AdvSendAdvert on;
  MaxRtrAdvInterval 60;

  prefix ::/64
  {
    AdvValidLifetime 600;
    AdvPreferredLifetime 120;
  };

  RDNSS fe80::c24a:ff:fe9f:89fc {};
  DNSSL stupin.su {};
};
Включим и запустим демона:
# systemctl enable radvd
# systemctl start radvd
Теперь устройства в локальной сети могут запрашивать префикс у нашего компьютера. Если на компьютере настроена фильтрация пакетов, то для того, чтобы клиенты смогли обращаться к radvd, нужно разрешить входящие UDP-пакеты на порт 58:
# ip6tables -A INPUT -i eth0 -p udp -m udp --dport 58  -j ACCEPT
Если устройства в локальной сети должны иметь доступ к DNS-серверу, то нужна пара правил, полностью аналогичных таковым для IPv4:
# ip6tables -A INPUT -p udp -m udp --dport 53 -j ACCEPT
# ip6tables -A INPUT -p tcp -m tcp --dport 53 -j ACCEPT
Чтобы разрешить пропускать трафик из локальной сети во внешнюю сеть, нужно при помощи команды sysctl выставить несколько настроек:
# sysctl -w net.ipv6.conf.all.forwarding=1
# sysctl -w net.ipv6.conf.default.forwarding=1
Чтобы эти настройки применялись автоматически при перезагрузке компьютера, нужно вписать их в файл /etc/sysctl.conf:
net.ipv6.conf.all.forwarding=1
net.ipv6.conf.default.forwarding=1
Если на компьютере настроена фильтрация пакетов, можно разрешить движение трафика между локальным и внешним интерфейсами. Например, я разрешил только движение пакетов ICMPv6:
# ip6tables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# ip6tables -A FORWARD -p icmpv6 -m icmpv6 ! --icmpv6-type redirect -j ACCEPT
# ip6tables -P FORWARD DROP
Ноутбуку, который будет подключаться к сети, кое-что всё-таки разрешим:
# ip6tables -A FORWARD -i wlan0 -p tcp -m multiport --dports 21,22,25,43,80,110,143,443,587,993,995,5222,5223 -j ACCEPT
После изменения настроек фильтрации пакетов не забудьте записать их:
# ip6tables-save > /etc/network/ip6tables
Теперь настала очередь настройки системы на ноутбуке. Во-первых, поставим rdnssd для получения адресов DNS-серверов:
# apt-get install rdnssd
Включим и запустим его:
# systemctl enable rdnssd
# systemctl start rdnssd
Для правильной работы rndssd понадобится также пакет resolvconf. Если он ещё не установлен в системе, нужно его поставить:
# apt-get install resolvconf
Теперь пропишем в файле /etc/network/interfaces, как нужно настраивать IPv6 на интерфейсе wlan0:
iface wlan0 inet6 auto
  privext 2
Т.к. к префиксу, полученному через RA, добавляется идентификатор интерфейса, полученный из MAC-адреса компьютера, то MAC-адрес служит отличным идентификатором, по которому можно отслеживать интернет-активность конкретного устройства. Этот идентификатор остаётся постоянным даже при смене провайдера. Опция privext включает генерацию случайного идентификатора интерфейса, а также приоритетное использование IPv6-адреса, полученного из этого идентификатора. Таким крупным компаниям как Google это нисколько не помешает отслеживать активность устройства, но и облегчать задачу всем подряд тоже не стоит.

Осталось отключиться от WiFi-сети и снова подключиться к ней и проверить доступность IPv6-ресурсов с ноутбука:
# ifdown wlan0
# ifup wlan0
Заходим на указанные выше ресурсы для тестирования IPv6 и любуемся результатами. В этом случае по второй ссылке удалось набрать лишь 17 баллов, т.к. ICMPv6-запросы были слишком медленными и не успевали отрабатывать за отведённое время.

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

IPv6 от Дом.ru (ЭР-Телеком) на сервере Ubuntu/Debian

воскресенье, 18 августа 2019 г.

Сборка плагинов и тем Wordpress в deb-пакеты

Как я уже говорил, я предпочитаю устанавливать программное обеспечение в систему с использованием системного пакетного менеджера. В простейших случаях пакеты можно собирать, формируя файлы и каталоги, образующие структуру, которую понимает команда dpkg-deb, формирующая двоичные пакеты. Команды, формирующие необходимую структуру каталогов и вызывающие dpkg-deb, можно оформить в виде скрипта. В некоторых случаях такой скрипт удаётся обобщить для сборки ряда однотипных приложений.

Например, плагины и темы на официальном сайте wordpress располагаются по ссылкам вполне предсказуемого формата. Благодаря этому, возможно обобщить скрипт сборки deb-пакетов с плагинами и темами для Wordpress. Получившийся скрипт оказывается способен собирать deb-пакеты с любыми плагинами и темами, имеющимися на официальном сайте:
#!/bin/sh

build()
{
TYPE="$1"
NAME="$2"
VERSION="$3"
DEPENDS="$4" || ""

if [ -n "$DEPENDS" ]
then
  DEPENDS=", $DEPENDS"
fi

case "$TYPE" in
  plugin)
    PACKAGE="wordpress-$NAME"
    ;;

  theme)
    PACKAGE="wordpress-theme-$NAME"
    ;;

  *)
    echo "$TYPE $NAME $VERSION: Unknown type"
    return 2
    ;;
esac

DIR="${PACKAGE}_${VERSION}"

wget --quiet https://downloads.wordpress.org/$TYPE/$NAME.$VERSION.zip 2>/dev/null \
  || wget --quiet https://downloads.wordpress.org/$TYPE/$NAME.zip 2>/dev/null \
  || (echo "$TYPE $NAME $VERSION: Failed to download ZIP-file" >/dev/stderr && return 1)

unzip -q $NAME.$VERSION.zip 2>/dev/null \
  || unzip -q $NAME.zip 2>/dev/null \
  || (echo "$TYPE $NAME $VERSION: Failed to unpack ZIP-file" > /dev/stderr && return 1)

rm $NAME.$VERSION.zip 2>/dev/null || rm $NAME.zip

mkdir -p $DIR/DEBIAN

# Формируем описание пакета
cat - > $DIR/DEBIAN/control <<END
Package: $PACKAGE
Version: $VERSION
Architecture: all
Depends: wordpress$DEPENDS
Maintainer: Vladimir Stupin <vladimir@stupin.su>
Description: $NAME $TYPE for Wordpress
 $NAME $TYPE for Wordpress
 Homepage: https://wordpress.org/${TYPE}s/$NAME/
END

chmod 755 $DIR/DEBIAN/control

cat - > $DIR/DEBIAN/postinst <<END
#!/bin/sh
# postinst script for Wordpress $TYPE $NAME

set -e

# summary of how this script can be called:
#        * <postinst> \`configure' <most-recently-configured-version>
#        * <old-postinst> \`abort-upgrade' <new version>
#        * <conflictor's-postinst> \`abort-remove' \`in-favour' <package>
#          <new-version>
#        * <deconfigured's-postinst> \`abort-deconfigure' \`in-favour'
#          <failed-install-package> <version> \`removing'
#          <conflicting-package> <version>
# for details, see http://www.debian.org/doc/debian-policy/ or
# the debian-policy package

case "\$1" in
  configure)
        # Change permissions to application files
        if ! dpkg-statoverride --list /usr/share/wordpress/wp-content/${TYPE}s/$NAME >/dev/null; then
                chown -R root:root /usr/share/wordpress/wp-content/${TYPE}s/$NAME
                chmod 755 /usr/share/wordpress/wp-content/${TYPE}s/$NAME
                find /usr/share/wordpress/wp-content/${TYPE}s/$NAME -type d | xargs chmod 755
                find /usr/share/wordpress/wp-content/${TYPE}s/$NAME -type f | xargs chmod 644
        fi
  ;;
  abort-upgrade|abort-remove|abort-deconfigure)
  ;;
  *)
    echo "postinst called with unknown argument \\\`\$1'" >&2
    exit 1
  ;;
esac
END

chmod 0755 $DIR/DEBIAN/postinst

# Формируем содержимое пакета
mkdir -p $DIR/usr/share/wordpress/wp-content/${TYPE}s/
cp -R $NAME $DIR/usr/share/wordpress/wp-content/${TYPE}s/
rm -R $NAME

FILE=${PACKAGE}_${VERSION}_all.deb

# Формируем пакет
dpkg-deb -b $DIR $FILE >/dev/null 2>&1
rm -R $DIR

echo "$TYPE $NAME $VERSION: OK"
return 0
}

build plugin simple-ldap-login 1.6.0 php-ldap
build theme period 1.270
build plugin the-events-calendar 4.2.7
build plugin wise-chat 2.7
build plugin site-sticky-notes 1.1.0
build plugin so-widgets-bundle 1.9.10
Скрипт состоит из одной функции, которая собственно и занимается сборкой пакета, и вызовов этой функции для сборки нескольких плагинов и тем. Скрипт использует для работы утилиты wget, unzip и dpkg-deb. При отсутствии в системе wget или unzip, их можно установить из одноимённых пакетов.

В скрипте выше указаны версии плагинов, работающие с Wordpress версии 3.6. У плагина simple-ldap-login указана также дополнительная зависимость - пакет php-ldap. Вы можете указать в скрипте свой список плагинов и тем, указав их версии и дополнительные зависимости. При необходимости указать несколько зависимостей, нужно перечислить их через запятую и заключить в кавычки.

воскресенье, 11 августа 2019 г.

Создание своего репозитория Debian при помощи aptly

Несмотря на то, что в официальных репозиториях Debian можно найти почти всё, что может понадобиться, иногда всё-таки приходится самостоятельно собирать пакеты. Я стараюсь не замусоривать систему программами, установленными из исходных текстов и модулями различных языков программирования, установленными через их стандартные инструменты, вроде cpan, pip или gem.

Собирать пакеты приходится по нескольким причинам:
  • В репозитории нет нужного пакета. Иногда бывает можно найти неофициальные репозитории для нужного релиза операционной системы.
  • Через репозитории дистрибутива доступна только старая версия пакета, в которой не хватает нужных функций. В этом случае иногда помогают официальные репозитории backport с более свежими версиями пакетов, собранными для предыдущих релизов операционной системы.
  • В пакете из репозитория есть ошибка. Даже в стабильном дистрибутиве встречаются ошибки, которые не исправляются ментейнерами. Тут придётся либо обновлять пакет, либо исправлять в нём ошибку.
  • В репозитории есть нужная версия пакета, но собранная без поддержки определённой функции. Тут без вариантов - нужно дорабатывать и пересобирать пакет.
Например, в 2013 году я начал настраивать на работе серверы под Debian Wheezy. С тех пор у меня накопилось некоторое количество самосборных пакетов:
  • python-grab - не было в репозитории
  • python-flask-httpauth - не было в репозитории
  • python-pycurl - не было в репозитории
  • libdancer-plugin-database-core-perl - не было в репозитории
  • libnet-ssh-expect-perl - не было в репозитории
  • wordpress-plugin-simple-ldap-login - не было в репозитории
  • python-mysqldb - модуль версии 1.2.3 заменён на более свежую версию 1.2.5 из-за некорректной работы с collation utf8_bin: python-mysqldb: utf8_bin collation will not convert to Unicode strings
  • libsnmp и компания - пакеты были пересобраны из-за того, что не все производители оборудования с поддержкой SNMPv3 обеспечивают монотонный рост счётчика количества перезагрузок и времени с момента перезагрузки: Стандарт SNMPv3 и суровая действительность USM_TIME_WINDOW
  • python-netsnmp - модуль пересобран из-за ошибки в формировании SET-запросов со значениями типа IPv4: Исправление Python-прослойки библиотеки Net-SNMP
  • php5 и компания - среди этих пакетов был пакет php5-snmp, зависящий от пакета libsnmp
  • openntpd - в пакете была иcправлена ошибка в обработке таймаута ответов от серверов DNS: Таймаут DNS в OpenNTPd
  • uwsgi - включена поддержка Linux capabilities: Пересборка uwsgi с поддержкой Linux Capabilities
  • zabbix - собрана более свежая версия пакета, наложена серия нестандартных патчей. Например, добавлены отдельные настройки таймаута и количества повторов для опроса по протоколу SNMP: Установка и настройка Zabbix 2.2.0 в Debian Wheezy
  • python-paramiko и python-ecdsa - модуль paramiko 1.7.7.1-3.1 обновлён до версии 1.16.0-1, т.к. версия paramiko из репозитория имела ошибки в поддержке протокола SFTP: python-paramiko: sftp connections hangs. Модуль python-ecdsa был тоже обновлён, т.к. более новая версия модуля paramiko требовала и более свежую версию модуля ecdsa.
Если у вас есть только один компьютер и вам нужна лишь пара нестандартных программ, то заморачиваться сборкой пакетов, возможно, не стоит. Когда же нестандартных программ набирается с десяток, их приходится периодически обновлять, а потом ещё и устанавливать на десяток компьютеров, то может оказаться проще потратить время на изучение различных способов сборки пакетов и организацию собственного репозитория.

Есть масса способов собрать пакет для Debian:
  • Можно доработать пакет с исходными текстами и собрать как новый пакет с исходными текстами, так и собрать из него новые двоичные пакеты.
  • Для сборки пакетов с модулями python существует утилита python-stdeb: Создание deb-пакетов для модулей Python
  • Для сборки пакетов с модулями perl существует утилита dh-make-perl.
  • Можно попробовать сконвертировать готовый пакет из другого формата при помощи утилиты alien.
  • Можно попробовать собрать пакет при помощи утилиты checkinstall.
  • Наконец, можно просто создать пакет вручную при помощи утилиты dpkg-deb: 5.13 Как мне сделать собственный .deb пакет?
Собственный репозиторий позволяет экономить время, т.к. становится возможным не думать о том, установлены ли в системе пакеты нужных версий со всеми доработками: нужно всего-лишь прописывать в список подключенных репозиториев на каждой системе строчку со своим репозиторием.

Когда утилита aptly ещё не попалась мне на глаза, я делал репозитории при помощи утилиты apt-ftparchive. Не помню подробностей, но точно помню, что была она не очень удобной в использовании.

Потом мне попалась утилита aptly, которая обладала более богатыми возможностями и была более удобна в использовании. Стоит, правда, сказать, что утилита настолько богата возможностями, что удобство использования остаётся довольно относительным. Обновлять репозитории мне приходилось нечасто, поэтому каждый раз я с трудом вспоминал, какими командами я пользовался в прошлый раз. Я стал записывать команды в wiki-страницу и пользоваться своими заметками. Правда, структурированы они были довольно плохо, поэтому в блог я ничего не публиковал. Теперь же решил упорядочить эти заметки и выложить их в блог.

Установка пакета

Установим утилиту:
# apt-get install aptly
Утилита написана на языке go, в скомпилированном виде тянет за собой минимум зависимостей: это утилиты для работы с архивами и утилиты для манипуляции GPG-подписями.

Создание локальных репозиториев

Список репозиториев пока пуст:
$ aptly repo list
No local repositories found, create one with `aptly repo create ...`.
Создадим новый репозиторий с именем stretch:
$ aptly repo create stretch

Local repo [stretch] successfully added.
You can run 'aptly repo add stretch ...' to add packages to repository.

Добавление пакетов в локальный репозиторий

Добавляем в репозиторий stretch все двоичные пакеты из текущего каталога:
$ aptly repo add stretch *.deb
Loading packages...
[+] libauthen-radius-perl_0.26-1ufanet_all added
[+] libnet-ssh-expect-perl_1.09-1_all added
[+] zabbix-agent-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-agent_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-frontend-php_1:3.4.12-1+stretch-ufanet2_all added
[+] zabbix-get-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-get_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-java-gateway_1:3.4.12-1+stretch-ufanet2_all added
[+] zabbix-proxy-mysql-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-proxy-mysql_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-proxy-pgsql-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-proxy-pgsql_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-proxy-sqlite3-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-proxy-sqlite3_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-sender-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-sender_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-server-mysql-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-server-mysql_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-server-pgsql-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-server-pgsql_1:3.4.12-1+stretch-ufanet2_amd64 added
Следом добавляем в репозиторий stretch все пакеты с исходными текстами из текущего каталога:
$ aptly repo add stretch *.dsc
Loading packages...
[+] libauthen-radius-perl_0.26-1ufanet_source added
[+] libnet-ssh-expect-perl_1.09-1_source added
[+] zabbix_1:3.4.12-1+stretch-ufanet2_source added
По умолчанию программа размещает пакеты и базу данных в каталоге ~/.aptly

Публикация локальных репозиториев

Список опубликованных репозиториев пока пуст:
$ aptly publish list
No snapshots/local repos have been published. Publish a snapshot by running `aptly publish snapshot ...`.
Если сейчас попытаться опубликовать репозиторий, то программа сообщит о том, что не указан дистрибутив:
$ aptly publish repo stretch ncc
ERROR: unable to publish: unable to guess distribution name, please specify explicitly
Посмотрим текущие настройки репозитория:
$ aptly repo show stretch
Name: stretch
Comment: 
Default Distribution: 
Default Component: main
Number of packages: 23
Укажем дистрибутив репозитория:
$ aptly repo edit -distribution="stretch" stretch
Local repo [stretch] successfully updated.
Опубликуем дистрибутив без PGP-подписи:
$ aptly publish repo -skip-signing=true stretch ncc
Loading packages...
Generating metadata files and linking package files...
Finalizing metadata files...

Local repo stretch has been successfully published.
Please setup your webserver to serve directory '/home/stupin/.aptly/public' with autoindexing.
Now you can add following line to apt sources:
  deb http://your-server/ncc/ stretch main
  deb-src http://your-server/ncc/ stretch main
Don't forget to add your GPG key to apt with apt-key.

You can also use `aptly serve` to publish your repositories over HTTP quickly.
Посмотрим список опубликованных репозиториев:
$ aptly publish list Published repositories:

ncc/stretch [amd64, source] publishes {main: [stretch]}
Опубликованные репозитории находятся в каталоге ~/.aptly/public, откуда их можно скопировать и разместить на веб-сервере. В примере выше локальный репозиторий stretch был опубликован в каталоге ncc, поэтому найти его можно будет в каталоге ~/.aptly/public/ncc

Поиск и удаление пакетов из локального репозитория

Для поиска пакетов, в имени которых присутствует слово zabbix и версия которых выше 1:3.4, а также пакетов, в имени которых присутствует подстрока -perl, можно воспользоваться следующей командой:
$ aptly package search '(Name (~zabbix), Version (>1:3.4)) | Name (~-perl)'
Как видно, скобки используются для группировки выражений, запятая соответствует логическому И, вертикальная черта соответствует логическому ИЛИ. Тильда обозначает совпадение с регулярным выражением, знак больше используется в своём обычном смысле. Более подробно правила фильтрации описаны в документации на странице: Package queries.

Для удаления пакетов из репозиториев используются те же самые выражения, которые используются при поиске пакетов. Например, удалим все пакеты, в имени которых присутствует подстрока zabbix:
$ aptly repo remove stretch 'Name (~zabbix)'
Loading packages...
[-] zabbix-proxy-mysql_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-proxy-mysql-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-sender-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-proxy-sqlite3-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-server-mysql-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix_1:3.4.12-1+stretch-ufanet2_source removed
[-] zabbix-java-gateway_1:3.4.12-1+stretch-ufanet2_all removed
[-] zabbix-server-pgsql_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-agent-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-proxy-pgsql-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-server-mysql_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-get_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-frontend-php_1:3.4.12-1+stretch-ufanet2_all removed
[-] zabbix-sender_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-get-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-server-pgsql-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-agent_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-proxy-sqlite3_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-proxy-pgsql_1:3.4.12-1+stretch-ufanet2_amd64 removed
Для удаления пакетов, в имени которых есть подстрока -perl, можно воспользоваться следующей командой:
$ aptly repo remove stretch 'Name (~-perl)'
Loading packages...
[-] libnet-ssh-expect-perl_1.09-1_all removed
[-] libauthen-radius-perl_0.26-1ufanet_source removed
[-] libauthen-radius-perl_0.26-1ufanet_all removed
[-] libnet-ssh-expect-perl_1.09-1_source removed

Перемещение пакетов между локальными репозиториями

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

Например, вот так можно выполнить перемещение всех пакетов, у которых в версии фигурирует подстрока stretch, из репозитория wheezy в репозиторий stretch:
$ aptly repo move wheezy stretch 'Version (~stretch)'
Loading packages...
[o] zabbix-server-pgsql-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-sender_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-proxy-pgsql-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-get-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-server-pgsql_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-proxy-pgsql_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-frontend-php_1:3.4.12-1+stretch-ufanet2_all moved
[o] zabbix-get_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-proxy-sqlite3-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-proxy-mysql_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-server-mysql-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-java-gateway_1:3.4.12-1+stretch-ufanet2_all moved
[o] zabbix-server-mysql_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-sender-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-proxy-sqlite3_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-agent-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-agent_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-proxy-mysql-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 moved

Обновление опубликованного репозитория

После первичной публикации репозитория, его нельзя опубликовать повторно. Чтобы изменения в репозитории применились к опубликованному репозиторию, нужно вызвать команду обновления:
$ aptly publish update -skip-signing=true stretch ncc
Loading packages...
Generating metadata files and linking package files...
Finalizing metadata files...
Cleaning up prefix "ncc" components main...
Publish for local repo ncc/stretch [amd64, source] publishes {main: [stretch]} has been successfully updated.

Удаление устаревших пакетов

После удаления пакетов из репозитория, они не удаляются из хранилища автоматически. Во-первых, пакет остаётся в хранилище до тех пор, пока на него ссылается хотя бы один из репозиториев - локальных или опубликованных. Во-вторых, даже если на пакет больше нет ссылок, он не будет удалён до тех пор, пока не будет явно вызвана команда очистки:
$ aptly db cleanup
Loading mirrors, local repos, snapshots and published repos...
Loading list of all packages...
Deleting unreferenced packages (23)...
Building list of files referenced by packages...
Building list of files in package pool...
Deleting unreferenced files (29)...
Disk space freed: 33.61 MiB...
Compacting database...

Удаление опубликованных репозиториев

Для удаления опубликованного репозитория можно воспользоваться соответствующей командой:
$ aptly publish drop stretch ncc
Removing /home/stupin/.aptly/public/ncc/dists...
Removing /home/stupin/.aptly/public/ncc/pool...

Published repository has been removed successfully.
Можно посмотреть в список опубликованных репозиториев и убедиться, что репозитория больше нет среди опубликованных:
$ aptly publish list
No snapshots/local repos have been published. Publish a snapshot by running `aptly publish snapshot ...`.

Удаление локального репозитория

Посмотрим на список локальных репозиториев:
$ aptly repo list
List of local repos:
 * [stretch] (packages: 0)

To get more information about local repository, run `aptly repo show <name>`.
Как видно, в репозитории нет пакетов, поэтому его можно удалить.

Для удаления локального репозитория stretch можно воспользоваться такой командой:
$ aptly repo drop stretch
Local repo `stretch` has been removed.
Посмотрим на список локальных репозиториев снова:
$ aptly repo list
No local repositories found, create one with `aptly repo create ...`.

P. S.

Есть у собственного репозитория и отрицательные стороны. Если у вас нет времени на то, чтобы обновить операционную систему до нового релиза, то собственный репозиторий с нестандартными пакетами будет ещё одним доводом в пользу того, чтобы как можно дольше оттягивать момент обновления операционной системы до нового релиза. С одной стороны, в новом релизе часть проблемых пакетов обновится и их больше не нужно будет пересобирать, а с другой стороны - всё равно нужно будет пересобрать доработанные пакеты и собрать пакеты, которых нет в стандартном репозитории обновлённой системы. И ещё при обновлении существует вероятность, что в новых пакетах будут не только исправлены старые ошибки, но и внесены новые.

Так, на работе всё тот же Debian Wheezy, который вышел в 2013 году, использовался мной аж до 2019 года. За это время успело выйти три новых релиза: Squeezy, Jessie, Stretch. Некоторые коллеги удивлялись, почему я продолжаю использовать этот устаревший релиз. Друге даже не догадывались об этом, т.к. им достаточно, чтобы обновлялся фасад здания, а с ржавыми трубами им дела иметь не приходилось. В 2018 году завершился срок продлённой поддержки Debian Wheezy. В 2019 году репозиторий пропал с зеркал и стало очевидно, что оттягивать обновление больше нельзя. Пришлось отложить всю текущую работу и заняться обновлениями, несмотря молчаливое и явное недовольство окружающих. Нельзя бесконечно копить технический долг - иногда нужно его возвращать.

воскресенье, 4 августа 2019 г.

Установка и настройка Gitea

Gitea - это веб-интерфейс для работы с репозиториями git. Приложение является форком приложения Gogs, и как и его родитель, написано на языке программирования Go. Приложение не требовательно к системным ресурсам, поэтому хорошо подходит для организации репозиториев исходных текстов небольших команд разработчиков.

Сборка приложения

Я собираю приложение на Debian Stretch, в котором имеется компилятор go версии 1.7. Для сборки приложения Gitea версии 1.8.1 потребуется компилятор версии 1.9 или выше. Если попытаться собрать приложение компилятором версии 1.7, то сборка завершится такой ошибкой:
package math/bits: unrecognized import path "math/bits" (import path does not begin with hostname)
Для установки компилятора подходящей версии воспользуемся репозиториями с бэкпортами Debian. На момент написания статьи в репозитории имеется компилятор версии 1.11. Пропишем в файл /etc/apt/sources.list репозиторий с бэкпортами для Stretch:
deb http://mirror.yandex.ru/debian stretch-backports main contrib non-free
Обновим список пакетов, доступных через репозитории:
# apt-get update
Установим свежую версию компилятора языка go из репозитория с бэкпортами:
# apt-get install -t stretch-backports golang-go
Если в системе ещё не установлен пакет git, то установим его:
# apt-get install git
Создадим каталог для сборки приложения и перейдём в него:
$ mkdir ~/gitea
$ cd ~/gitea
Скачиваем исходные тексты приложения в созданный каталог:
$ env GOPATH=/home/stupin/gitea/ go get code.gitea.io/gitea
Перейдём в git-репозиторий со скачанными исходными текстами:
$ cd ~/gitea/src/code.gitea.io/gitea
И переключимся на исходные тексты gitea версии 1.8.1:
$ git checkout v1.8.1
Полный список доступных веток и меток релизов можно посмотреть при помощи команд:
$ git branch -a
$ git tag -l
Перед тем, как запустить сборку, исправим ошибку в файле Makefile. В этом файле такую строчку:
export PATH := $($(GO) env GOPATH)/bin:$(PATH)
Нужно заменить на вот такую:
export PATH := $(GOPATH)/bin:$(PATH)
Если этого не сделать, то сборка прервётся с таким вот сообщением об ошибке:
modules/options/options.go:7: running "go-bindata": exec: "go-bindata": executable file not found in $PATH
modules/public/public.go:20: running "go-bindata": exec: "go-bindata": executable file not found in $PATH
modules/templates/templates.go:7: running "go-bindata": exec: "go-bindata": executable file not found in $PATH
Makefile:102: ошибка выполнения рецепта для цели «generate»
make: *** [generate] Ошибка 1
Собираем приложение:
$ env GOPATH=/home/stupin/gitea TAGS="bindata sqlite sqlite_unlock_notify" make generate build
Тег bindata включает сборку единого двоичного файла, включающего в себя все необходимые зависимости и файлы. Теги sqlite и sqlite_unlock_notify включают поддержку базы данных SQLite3. Описание всех тегов можно найти на странице Installation from source, в разделе Build.

В результате сборки в каталоге ~/gitea/src/code.gitea.io/gitea должен появиться файл с именем gitea.

Установка и настройка запуска приложения

Создаём пользователя gitea, от имени которого будет работать приложение:
# useradd -c 'Gitea' -r -M -d /opt/gitea gitea
Создаём каталог приложения gitea и копируем в него только то, что необходимо для работы приложения:
# cd /opt
# mkdir gitea
# cd gitea
# cp /home/stupin/gitea/src/code.gitea.io/gitea/gitea .
Создаём каталог для файла конфигурации:
# mkdir -p cusotm/conf/
Создаём файл /opt/gitea/custom/conf/app.ini с настройками приложения:
[server]
DOMAIN = stupin.su
HTTP_ADDR = 127.0.0.1
HTTP_PORT = 3000
ROOT_URL = https://stupin.su/git/
DISABLE_SSH = false
SSH_PORT = 22
OFFLINE_MODE = false
Чтобы настройки приложения в дальнейшем можно было поменять в процессе первоначальной настройки через веб-интерфейс, дадим пользователю gitea доступ к файлу конфигурации:
# chown gitea:gitea /opt/gitea/custom/conf/app.ini
Создаём service-файл /etc/systemd/system/gitea.ini для запуска/остановки приложения через systemd:
[Unit]
Description=Gitea (Git frontend)
After=syslog.target
After=network.target
;After=postgresql.service
After=nginx.service

[Service]
Type=simple
User=gitea
Group=gitea
WorkingDirectory=/opt/gitea
ExecStart=/opt/gitea/gitea web
Restart=always
Environment=USER=gitea HOME=/opt/gitea
           
[Install]
WantedBy=multi-user.target
Добавляем сервис в автозапуск при загрузке операционной системы:
# systemctl enable gitea.service
И запускаем его прямо сейчас:
# systemctl start gitea.service
Если после запуска сервис переходит в аварийное состояние, стоит заглянуть в журнал /var/log/daemon.log. Поищите сообщение следующего вида:
panic: fail to set message file(en-US): open conf/locale/locale_en-US.ini: no such file or directory
Если такое сообщение есть, то приложение было собрано без тега bindata. Я собирал приложение без этого тега, прежде чем исправил ошибку в файле Makefile. После этого я собрал приложение с тегом bindata, но система сборки не заметила разницы и скомпоновала исполняемый файл из уже готовых объектных файлов. Чтобы очистить имеющиеся файлы и собрать исполняемый файл снова, нужно выполнить следующие команды:
$ cd /home/stupin/gitea/src/code.gitea.io/gitea/
$ env GOPATH=/home/stupin/gitea TAGS="bindata sqlite sqlite_unlock_notify" make clean generate build

Настройка проксирования через nginx

Если приложение планируется запускать на выделенном домене, то для настройки проксирования внешних запросов с https на локальный адрес http при помощи nginx в общем случае нужно создать следующую конфигурацию в файле /etc/nginx/sites-enabled/gitea:
server {
    listen 80;
    server_name example.com;
    return 302 https://$server_name$request_uri;
}

server {
    listen 443 ssl;
    server_name example.com;
    
    ssl_certificate /path/to/certificate.crt;
    ssl_certificate_key /path/to/certificate_key.key;
    
    location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_pass http://localhost:3000;
    }
}
И подключить её командой:
# sudo ln -s /etc/nginx/sites-available/gitea /etc/nginx/sites-enabled/gitea
Я же разместил приложение на одном домене с другими приложениями, в отдельном каталоге. Для настройки впишем в файл /etc/nginx/sites-enabled/root настройки проксирования запросов с адреса https://stupin.su/git/ на адрес http://127.0.0.1/git/:
# Gitea
location /git/ {
    proxy_redirect off;
    proxy_bind 127.0.0.1;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_pass http://127.0.0.1:3000/;
}

Настройка приложения

Переходим по ссылке https://stupin.su/git/install и задаём настройки:
Настройки базы данных
  Тип базы данных: SQLite3
  Путь: /opt/gitea/data/gitea.db

Основные настройки
  Название сайта: Исходные тексты программ Владимира Ступина
  Путь корня репозитория: /opt/gitea/gitea-repositories
  Корневой путь Git LFS: /opt/gitea/data/lfs
  Запуск от имени пользователя: gitea
  Домен SSH сервера: stupin.su
  Порт SSH сервера: 22
  Gitea HTTP порт: 3000
  Базовый URL-адрес Gitea: https://stupin.su/git/
  Путь к журналу: /opt/gitea/log

Расширенные настройки
  Настройки Email
    Узел SMTP: mail.stupin.su:587
    Отправлять Email от имени: gitea@stupin.su
    SMTP логин: gitea@stupin.su
    SMTP пароль: mailbox_password
    Требовать подтверждения по электронной почте для регистрации: Да
    Разрешить почтовые уведомления: Да
  Сервер и настройки внешних служб
    Включить локальный режим: Нет
    Отключить Gravatar: Да
    Включить федеративные аватары: Нет
    Включение входа через OpenID: Нет
    Отключить самостоятельную регистрацию: Да
    Разрешить регистрацию только через сторонние сервисы: Нет
    Включить саморегистрацию OpenID: Нет
    Включить CAPTCHA: Нет
    Требовать авторизации для просмотра страниц: Нет
    Скрывать адреса электронной почты по умолчанию: Нет
    Разрешить создание организаций по умолчанию: Да
    Включение отслеживания времени по умолчанию:  Да
  Скрытый почтовый домен:
  Настройки учётной записи администратора
    Логин администратора: stupin
    Пароль: 
    Подтвердить пароль:
    Адрес эл. почты: vladimir@stupin.su
После сохранения этих настроек файл /opt/gitea/custom/conf/app.ini примет следующий вид:
APP_NAME = Исходные тексты программ Владимира Ступина
RUN_USER = gitea
RUN_MODE = prod

[server]
DOMAIN           = stupin.su
HTTP_ADDR        = 127.0.0.1
HTTP_PORT        = 3000
ROOT_URL         = https://stupin.su/git/
DISABLE_SSH      = false
SSH_PORT         = 22
OFFLINE_MODE     = false
SSH_DOMAIN       = stupin.su
LFS_START_SERVER = true
LFS_CONTENT_PATH = /opt/gitea/data/lfs
LFS_JWT_SECRET   = xxx

[oauth2]
JWT_SECRET = xxx

[security]
INTERNAL_TOKEN = xxx
INSTALL_LOCK   = true
SECRET_KEY     = xxx

[database]
DB_TYPE  = sqlite3
HOST     = 127.0.0.1:3306
NAME     = gitea
USER     = gitea
PASSWD   = 
SSL_MODE = disable
PATH     = /opt/gitea/data/gitea.db

[repository]
ROOT = /opt/gitea/gitea-repositories

[mailer]
ENABLED = true
HOST    = mail.stupin.su:587
FROM    = gitea@stupin.su
USER    = gitea@stupin.su
PASSWD  = mail_password

[service]
REGISTER_EMAIL_CONFIRM            = true
ENABLE_NOTIFY_MAIL                = true
DISABLE_REGISTRATION              = true
ALLOW_ONLY_EXTERNAL_REGISTRATION  = false
ENABLE_CAPTCHA                    = false
REQUIRE_SIGNIN_VIEW               = false
DEFAULT_KEEP_EMAIL_PRIVATE        = false
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
DEFAULT_ENABLE_TIMETRACKING       = true
NO_REPLY_ADDRESS                  = 

[picture]
DISABLE_GRAVATAR        = true
ENABLE_FEDERATED_AVATAR = false

[openid]
ENABLE_OPENID_SIGNIN = false
ENABLE_OPENID_SIGNUP = false

[session]
PROVIDER = file

[log]
MODE      = file
LEVEL     = Info
ROOT_PATH = /opt/gitea/log
Теперь для пущей безопасности, чтобы никто не подсмотрел в файле пароль почтового ящика mailbox_password и ключи xxx, которыми шифруются сеансовые данные, поменяем права доступа к файлу конфигурации:
# chmod ug=r,o= /opt/gitea/custom/conf/app.ini
Пример страницы обзора списка репозиториев: