Приватный репозиторий в Ubuntu

В Ubuntu репозитории – это специальные сервера-хранилища для приложений. Если Вы разрабатываете коммерческое приложение и запускаете его в Ubuntu, то логично положить его в репозиторий. А потом управлять этим приложением так же, как и обычными системными приложениями. Для этого нужно поднять в локальной сети или облаке apache, настроить логин и пароль, не забывать его обновлять… Но что если есть другой способ?

Облачные хранилища

С помощью apt-transport-s3 можно превратить bucket в приватный apt репозиторий. Однако, у этого способа появились следующие недостатки:

  • Некоторые адреса Амазона заблокированы в России
  • Данные находятся в Европе, поэтому может быть медленно.

Какие же есть альтернативы?

Самой известной считается Openstack Swift. Swift (OpenStack Object Storage) — это полностью распределенное «безграничное» хранилище, которое характеризуется отказоустойчивостью и высокой надежностью. Создано как конкурент Amazon S3. Его преимущества:

  • В России как минимум 2 провайдера предоставляют Swift как сервис: Clodo и Selectel
  • Данные находятся в России
  • Если Вы достаточно большие, то можете поднять его у себя
  • Все плюсы облачного хранилища: оплата за непосредственно используемые ресурсы, распределенное хранение, отказоустойчивость, 24/7.

Из недостатков можно выделить лишь полное отсутствие интеграции с Ubuntu. Это сложно назвать недостатком, если Вы программист. Поэтому я написал интеграцию сам: apt-transport-swift.

Разработка

Для начала нужно немного погрузиться в то, как apt взаимодействует с репозиториями. Для того чтобы получить информацию из репозитория, apt:

  • находит соответствующий метод из списка установленных. Все они лежат в папке: /usr/lib/apt/methods/
  • отправляет ему необходимые команды согласно протоколу

По умолчанию доступно достаточно много методов: http, ftp, cdrom, file, ssh и тд. Все они работают следующим образом:

  • каждый метод - это отдельная программа
  • на вход apt отправляет через stdin команды для выполнения
  • на выходе через stdout метод должен вернуть результат работы

Команды и ответы передаются в текстовом виде очень похожим на http. Например:

100 Capabilities
Version: 1.2
Pipeline: true
Send-Config: true

Эту команду отправляет метод, чтобы получить конфигурацию apt.conf:

600 URI Acquire
URI: swift://container/dists/stretch/InRelease
Filename: dists_stretch_InRelease
Expected-SHA1: 123
Last-Modified: Wed, 23 May 2018 14:13:16 GMT

Эту команду отправляет apt, когда необходимо скачать файл. Когда метод закончил скачивание, он возвращает:

201 URI Done
URI: swift://container/dists/stretch/InRelease
Filename: dists_stretch_InRelease
Expected-SHA1: 123
Size: 762361
Last-Modified: Wed, 23 May 2018 14:13:16 GMT

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

Всё вместе

В результате я получил следующую схему для Java проектов:

  1. Сборка .deb артефакта с помощью deb-maven-plugin. pom.xml:
<plugins>
...
	<plugin>
		<groupId>com.aerse.maven</groupId>
		<artifactId>deb-maven-plugin</artifactId>
		<version>1.4</version>
		<executions>
			<execution>
				<id>package</id>
				<phase>package</phase>
				<goals>
					<goal>package</goal>
				</goals>
			</execution>
		</executions>
		<configuration>
			<unixUserId>ubuntu</unixUserId>
			<unixGroupId>ubuntu</unixGroupId>
			<osDependencies>
				<openjdk-7-jdk></openjdk-7-jdk>
				<nginx></nginx>
			</osDependencies>
			<javaServiceWrapper>true</javaServiceWrapper>
			<fileSets>
				<fileSet>
					\<source>${basedir}/src/main/deb</source>
					<target>/</target>
				</fileSet>
			</fileSets>
		</configuration>
	</plugin>
...
</plugins>
  1. Дистрибьюция артефакта в apt репозиторий. Плагин проинициализирует репозиторий, если он изначально пустой. pom.xml:
<plugins>
...
  <plugin>
    <groupId>com.aerse.maven</groupId>
    <artifactId>apt-maven-plugin</artifactId>
    <version>1.9</version>
    <executions>
      <execution>
        <id>deploy</id>
        <goals>
          <goal>deploy</goal>
        </goals>
      </execution>
    </executions>
	<configuration>
		<component>main</component>
		<codename>myrepo</codename>
		<sign>true</sign>
		<keyname>name</keyname>
		<passphraseServerId>gpg.passphrase</passphraseServerId>
	</configuration>
  </plugin>
...
</plugins>

settings.xml:

<settings>
  ...
  <servers>
    ...
    <server>
      <id>gpg.passphrase</id>
      <passphrase>passphrase</passphrase>
    </server>
    ...
  </servers>
  ...
</settings>
  1. В maven есть такое понятие как wagon. Это особая точка расширения, позволяющая добавить новый протокол. С помощью swift-maven я добавил поддержку протокола swift. pom.xml:
<project>
  ...
  <distributionManagement>
    <repository>
      <id>private-repo</id>
      <url>swift://api.selcdn.ru/v3</url>
    </repository>
  </distributionManagement>
  ...

  <build>
    ...
    <extensions>
      ...
      <extension>
        <groupId>com.aerse</groupId>
        <artifactId>swift-maven</artifactId>
        <version>1.1</version>
      </extension>
      ...
    </extensions>
    ...
  </build>  
</project>

settings.xml:

<settings>
  ...
  <servers>
    ...
    <server>
      <id>private-repo</id>
      <username>username</username>
      <password>password</password>
    </server>
    ...
  </servers>
  ...
</settings>
  1. В качестве облачного хранения данных я выбрал Selectel. Они поддерживают API Swift v3.
  2. apt-transport-swift реализует swift протокол для apt.
#: cat /etc/apt/sources.list.d/privaterepo.list

deb swift://container myrepo main

И конфигурация:

#: cat /etc/apt/apt.conf.d/80privaterepo

Swift {
 Container0 {
   Name "container";
   URL "https://api.selcdn.ru";
   Username "username";
   Password "password";
 };
};

Результат

Хочется отметить, что схема не добавляет никаких новых сущностей в уже существующие инструменты. Всё реализовано в виде плагинов и должно работать независимо друг от друга. Например, с помощью apt-maven-plugin можно деплоить в S3.