deb is the format, as well as extension of the software package format for the Linux distribution Debian and its derivatives.

데비안과 데비안 계열 리눅스에서 사용하는 소프트웨어 패키지 파일, deb의 구조에 대해서 자세히 알아보자. 이를 알아본 후 최종적으로는 우리의 패키지 파일을 만드는 것이 목표가 되겠다.

소프트웨어 설치 도구를 사용한 간단한 패키지의 설치

편리한 어플리케이션의 설치, 이것을 리눅스의 장점 중 하나로 꼽는 사람이 많을 것이다. 컴퓨터가 인터넷에 연결되어 있다면 소프트웨어 설치 도구를 통해 필요한 소프트웨어를 아주 간단하게 설치할 수 있기 떄문이다.

각 리눅스 배포판별로 소프트웨어 설치 도구가 다 다르지만, 우분투 계열에서는 주로 apt-get을 사용한다. 그래서 패키지를 설치하려면 보통 다음과 같이 입력한다.

$ apt-get install <PACKAGE_NAME>

위 명령을 쓰면 사용자의 로컬에 저장되어 있는 소프트웨어 패키지 인덱스를 검색하여 그런 패키지가 존재하는지, 용량은 얼마나 되는지와 같은 잡다한 정보를 확인한 후, 결과가 정상적이면 해당 소프트웨어를 인터넷에서 내려받아 설치하게 된다. RHEL 계열 리눅스, 예를 들어 centos의 경우는 다음과 같이 yum을 실행한다.

$ yum install <PACKAGE_NAME>

apt-getyum은 동작 방식에 있어서 약간의 차이가 있지만 사용자 입장에서는 매우 편리한 것임에는 분명하고, 아무튼 이런 장점으로 인해 리눅스에서는 (패키지의 이름을 알고 있다면)소프트웨어의 설치가 매우 간단하다. 그래서 apt-get이나 yum을 사용하지 않는 다른 운영체제들, Windows나 mac에서도 이런 기능을 수행하는 프로그램이 잘 사용되고 있다.

정리해 보자면 다음과 같을 것이다.

  1. 인터넷 어딘가에는 내 운영체제에서 다운로드하여 설치하고 실행할 수 있을만한 어플리케이션을 “패키지"로 포장해 둔다.
  2. 내 운영체제에서는 이런 패키지를 다운로드하고 설치할 수 있는 “소프트웨어 설치 도구"가 있다.
  3. 소프트웨어 설치 도구를 사용하여 일련의 명령을 통해 패키지를 설치할 수 있다.

그렇다면 이런 과정은 어떻게 이루어지는지 조금 더 자세하 확인해 보자. 여기서는 ubuntu를 사용한 예를 가지고 확인해 보도록 한다.

deb 패키지파일은 어디 있나

우선 deb이 어떻게 소프트웨어를 설치하는지 알아보자. 우리는 우분투를 사용하여 패키지를 설치할 때 먼저 다음과 같은 명령어를 내려서 패키지 리스트의 캐시를 최신으로 업데이트하고 있다.

$ sudo apt-get update

기존:1 http://ap-northeast-2.ec2.archive.ubuntu.com/ubuntu xenial InRelease
받기:2 http://ap-northeast-2.ec2.archive.ubuntu.com/ubuntu xenial-updates InRelease [109 kB]
기존:3 https://download.docker.com/linux/ubuntu xenial InRelease
무시:4 http://dl.google.com/linux/chrome/deb stable InRelease
...

받기:31 http://security.ubuntu.com/ubuntu xenial-security/universe DEP-11 64x64 Icons [145 kB

위의 기존:1이 가리키는 주소에 접속해 보자. 원래는 중간 과정이 조금 있지만, 우선 pool이란 디렉토리에서 데비안 패키지 파일을 확인할 것이다. 그래서 http://ap-northeast-2.ec2.archive.ubuntu.com/ubuntu/pool에 접속하면 다음과 같은 화면을 볼 수 있다.

package-dist

패키지들은 적당한 디렉토리로 분리되어 있다. 만약 net-tools라는 패키지를 설치한다면 최종적으로, net-tools라는 디렉토리까지 찾은 후 거기서 해당 시스템에 맞는 패키지를 다운로드할 것이다.

현재 예를 든 우분투 패키지 저장소는 미러링 된 AWS의 우분투 패키지 저장소인데, 만약 지리적으로 해당 지역과의 거리가 먼 곳에서는 패키지를 다운로드하는 데 많은 시간이 들 것이다. 따라서 이런 미러링 저장소들은 세계 여러 곳곳에 존재한다. 한국에는 대표적으로 다음과 같은 곳이 많이 쓰인다.

  • KAIST
  • daumkakao

이 저장소의 위치는 리눅스 파일 /etc/apt/sources.list 파일, /etc/apt/sources.list.d/* 디렉토리 내 파일로 관리되고 있다. 즉 이 파일의 내용을 변경하는 것으로 지리적으로 가까운 저장소에서 패키지를 받도록 변경할 수 있으며, 또 누군가가 만든 임의의 저장소를 추가할 수도 있다.

어쨌든 apt-get update로 해당 패키지에 대한 정보를 얻었다면 이제 apt-get install을 통해 패키지를 설치할 수 있다.

deb의 구조: binary package

심플한 deb 예제: jq

단순한 예를 통해 굉장히 심플하게 패키지 구조를 알아보자. 그런 적당한 패키지로는 의존성도 크게 없고, 실행 파일 하나로 구성된 jq로 알아보는 것이 좋겠다.

deb의 압축 해제

apt-get의 옵션에는 패키지를 설치하지 않고, 해당 deb 패키지파일을 단순히 다운로드하는 기능도 존재한다. apt-get download가 그 역할을 수행한다.

$ sudo apt-get download jq
받기:1 http://ap-northeast-2.ec2.archive.ubuntu.com/ubuntu xenial-updates/universe amd64 jq amd64 1.5+dfsg-1ubuntu0.1 [144 kB]
내려받기 144 k바이트, 소요시간 0초 (469 k바이트/초)
$ ls -la
합계 160
drwxrwxr-x  2 user user   4096 11월 24 20:32 .
drwxr-xr-x 61 user user  12288 11월 24 20:34 ..
-rw-r--r--  1 root root 144168  9월 10 22:38 jq_1.5+dfsg-1ubuntu0.1_amd64.deb

패키지 파일은 ar 포맷으로 압축되어 있으므로, 명령어 ar을 사용하여 압축을 해제할 수 있다.

$ ar -xv jq_1.5+dfsg-1ubuntu0.1_amd64.deb
x - debian-binary
x - control.tar.gz
x - data.tar.xz
$ ls -la
합계 308
drwxrwxr-x  2 cublr cublr   4096 11월 24 20:35 .
drwxr-xr-x 61 cublr cublr  12288 11월 24 20:35 ..
-rw-r--r--  1 cublr cublr    932 11월 24 20:35 control.tar.gz
-rw-r--r--  1 cublr cublr 143044 11월 24 20:35 data.tar.xz
-rw-r--r--  1 cublr cublr      4 11월 24 20:35 debian-binary
-rw-r--r--  1 root  root  144168  9월 10 22:38 jq_1.5+dfsg-1ubuntu0.1_amd64.deb

data.tar.xz의 구성

압축을 해제하면 세 개의 파일이 나오는 것을 확인할 수 있다. 개중에 data.tar.xz, control.tar.gz 두 개의 압축된 파일도 있는데 이것도 각각 압축을 해제해 보자. control.tar.gz는 control에, data.tar.xz는 data에 압축을 해제하였다.

$ tree
.
├── control
│   ├── control
│   └── md5sums
├── control.tar.gz
├── data
│   └── usr
│       ├── bin
│       │   └── jq
│       └── share
│           ├── doc
│           │   └── jq
│           │       ├── AUTHORS
│           │       ├── README
│           │       ├── changelog.Debian.gz
│           │       └── copyright
│           └── man
│               └── man1
│                   └── jq.1.gz
├── data.tar
├── debian-binary
└── jq_1.5+dfsg-1ubuntu0.1_amd64.deb

data 디렉토리만 봐도 이제 느낌이 올 것이다. deb 파일은 단순하게, 설치되어야 하는 파일과 그 위치가 이미 지정된 채로 압축되어 있는 형태인 것이다. 즉 패키지를 설치한다는 것은 압축 해제해서 나온 결과를 리눅스 루트 디렉토리에 그대로 덮어쓴다는 의미이다.

따라서 개인이 어떤 커스텀 패키지를 만든다면 이런 형태로 구성될 것이다.

  1. 코드를 짜든가 어디서 실행파일을 가져오거나 해서, 바이너리를 준비한다.
  2. 현재 디렉토리를 루트로 생각한 기준으로, 바이너리를 적당한 위치에 둔다.
  3. 이를 data.tar.xz로 압축한다.
  4. 이를 다시 ar로 압축하여 deb 확장자를 추가한다.

control, 패키지 정보의 기록

control.tar.gz를 압축 해제하여 최종적으로 control과 md5sums 라는 파일을 얻는 것을 확인했다. md5sums 파일은 이름만 봐도 아마 바로 눈치를 챌 수 있을텐데, 당연히 패키지 파일에 대한 검증 목적으로 해시값이 들어있을 것이다. 그러면 control 파일은 내용이 어떤 것일까?

$ cat control/control
Package: jq
Version: 1.5+dfsg-1ubuntu0.1
Architecture: amd64
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Installed-Size: 341
Depends: libc6 (>= 2.14), libonig2 (>= 5.9.5)
Section: utils
Priority: optional
Multi-Arch: foreign
Homepage: https://github.com/stedolan/jq
Description: lightweight and flexible command-line JSON processor
 jq is like sed for JSON data – you can use it to slice
 and filter and map and transform structured data with
 the same ease that sed, awk, grep and friends let you
 play with text.
 .
 It is written in portable C, and it has zero runtime
 dependencies.
 .
 jq can mangle the data format that you have into the
 one that you want with very little effort, and the
 program to do so is often shorter and simpler than
 you’d expect.
Original-Maintainer: Simon Elsbrock <simon@iodev.org>

이것도 열어 보니 바로 느낌이 온다. 패키지 이름과 버전, 아키텍쳐 외 기타 다른 정보들이 잔뜩 저장되어 있었던 것이다. 각 필드에 대한 내용이 뭔지를 알아봐야겠지만, 사실 필드 이름에서 전부 유추 가능하다. 그래도 우리의 목표는 패키지 파일 만들기니까 이 필드들과 주요 필드들에 대해서, 이것들이 어떤 기능을 하는지 자세히 알아보는 것이 좋겠다.

상세한 내용은 데비안 패키지 정책에서 확인해 보자.

Package

The name of the binary package

이 패키지의 이름을 말한다. 주의 사항으로는 여기서는 바이너리 패키지를 다루지만 DEB 패키지에는 바이너리/소스 패키지 두 가지의 타입이 좋재한다. 바이너리와 소스 두 가지의 패키지가 같은 것이라면 이 패키지 이름이 서로 같아야 한다.

Version

The version number of a package.

이름대로 패키지의 버전 정보를 기록한다. 자세한 내용은 Semantic Versioning을 참고하자.

이 버전은 정확히 말하면 바이너리 버전이 아니라, 우분투에서 관리되는 패키지의 버전을 말하는 것이라 체계가 약간 복잡하다. 사실 아예 공식 데비안 패키지를 만들지 않을 바에야 그냥 적당히 맞추는 것도 괜찮겠다. 어쨌든 apt는 이 버전을 통해서 이미 설치된 패키지보다 새로 설치할 패키지가 더 최신의 버전이라는 것을 알게 된다.

필드의 포맷은 다음과 같다.

[epoch:]upstream_version[-debian_revision].

위 포맷으로 필드를 채워 버전 스트링을 만들게 되면 이제 어떤 패키지의 순서를 정할 수 있게 된다. 먼저 epoch를 서로 비교하고 서로 같은 경우 upstream_version을 비교한다. 이것도 같으면 당연히 debian_revision을 비교한다. epoch는 ‘숫자’타입으로 비교하고, upstream_version과 debian_revision은 스트링으로 비교한다. 스트링은 우리가 흔히 상식적으로 생각하는 그 순서로 비교한다는 것으로 생각하면 거의 맞지만, 특수 기호나 뭐 다른 부분이 있긴 있어서, 더 자세한 내용은 문서를 찾아보는 것이 빠르다.

epoch

되도록이면 작은 값의 unsigned integer를 넣는다. 만약 없다면 0을 넣는다. 만약 생략되는 경우는 colons도 같이 없어야 한다. : 이게 없어야 된다는 뜻이다.

$ dpkg -l
...
ii  udisks2                                2.7.6-3ubuntu0.2         amd64                    D-Bus service to access and manipulate storage devices
ii  ufw                                    0.35-5                   all                      program for managing a Netfilter firewall
ii  uim                                    1:1.8.6+gh20180114.64e31 amd64                    Universal Input Method - main binary package
...

udisks2, ufw는 epoch가 없지만, uim은 epoch가 존재한다.

jq는 epoch가 없다.

upstream_version

이건 실제 바이너리의 버전을 말한다. 사실 jq같은 경우 우분투에서만 사용할 수 있는 바이너리는 아니고 여기서 실제로 배포하는데, apt-get으로 편하게 설치할 수 있도록 적당히 문제 없고 검증된 버전을 대신 제공하고 있다. jq는 실제로 현재 1.6 버전을 배포하고 있지만, 우분투에서는 apt를 통해 1.5를 배포하는 것이다. 따라서 jq의 upstream_version은 1.5가 된다.

그러므로 우리가 패키지를 만들게 된다면 이게 실질적인 패키지의 버전이 될 것이다.

jq는 1.5+dfsg의 upstream_version을 가진다.

debian_revision

이거는 upstream_version 기준으로 이 패키지가 몇 번째의 패키지인지를 알려준다. 그러니까 jq같은 경우 1.5 업스트림 버전으로 1번만 패키지가 갱신되었다.

jq는 -1을 가진다.

Architecture

Architecture는 패키지가 사용하능한 아키텍쳐를 설명한다. 당장 떠오르는 아키텍쳐만 해도 다음과 같다.

  • amd64: x86-64 인텔 호환… 흔히 우리가 쓰는 PC
  • armhf: raspberry-pi3/odroid-u2 등의 기기

어쨌든 위에서 보았듯이 패키지 파일에는 컴파일된 실행 파일이 포함되므로 armhf에서 amd64 패키지를 설치해 보았지 플랫폼이 달라 실행되지 않을 것이다. 따라서 패키지가 어느 아키텍쳐에서 만들어졌는지 Architecture 필드로 명시한다.

그러면 아키텍쳐는 무엇이 있는지 전체 리스트를 확인하고 싶을 것이다. 다음 명령어로 확인할 수 있는데 우리 생각보다 아키텍쳐가 훨씬 많다.

$ dpkg-architecture -L
uclibc-linux-armel
uclibc-linux-i386
...
armhf
armel
...
arm64
...
...

어쨌든 dpkg-architecture로도 확인할 수 없는 특이한 아키텍쳐도 있다.

  • all: 아키텍쳐 상관없이 다 사용할 수 있는 패키지. 파이썬으로 만들어졌다거나, 폰트 패키지라거나 그런 것들.
  • source: 바이너리 패키지가 아니라 소스 패키지일 경우

jq는 amd64 architecture를 가진다고 되어 있다. 물론 이 글은 amd64 우분투에서 확인하여 작성하므로 그런거고, odroid-u2에서 jq를 확인하면 다음과 같다.

...
 Package: jq
 Version: 1.5+dfsg-1ubuntu0.1
 Architecture: armhf
...
Maintainer

이건 maintainer, 패키지를 관리하는 사람의 이름과 이메일 주소를 쓰면 된다. 그냥 쓰면 안되고 다 규격이 있다.(RFC822)

Your Name <yourname@myname.com>
Installed-size

설치되는 파일의 크기를 적는다. 바이너리 패키지일 때 이 필드를 사용하며, apt로 패키지 설치시 “xx만큼의 용량을 더 사용합니다” 등의 메시지가 다 이런걸 토대로 계산하여 알려주는 것이다. deb에 압축해서 넣을 모든 파일들의 byte를 적당히 합쳐서 1024로 나눈 값을 쓰면 된다.

jq는 341이며, 이는 이 패키지를 설치시 349184바이트만큼 디스크를 쓴다는 뜻이다.

그래서 우분투에서 뭔가 패키지를 설치하는 경우, 몇 메가바이트를 다운로드할 것이고, 디스크는 몇 사이즈를 더 쓸것이고… 하는게 다 이 필드를 보고 계산한 다음 얻는 정보라는 뜻이다.