Skip to content

SonatypeNexus:Apt

A Nexus Repository 3 plugin that allows usage of apt repositories.

Build

## Clone the project:
$ git clone https://github.com/sonatype-nexus-community/nexus-repository-apt
## Build the plugin:
$ cd nexus-repository-apt
$ mvn

Docker

Build with docker and create an image based on nexus repository 3

$ docker build -t nexus-repository-apt:3.11.0 .

Run a docker container from that image

$ docker run -d -p 8081:8081 --name nexus-repo nexus-repository-apt:3.11.0

Upload package

Manually upload a package to a new created repo:

$ curl -u user:pass -X POST -H "Content-Type: multipart/form-data" --data-binary "@package.deb" http://nexus_url:8081/repository/repo_name/

curl에서 --data-binary를 사용할 경우 파일명 앞에 @를 꼭 붙여야 한다.

How to use

APT Settings APT Browse 저장소 종류를 apt (hosted)로 선택한 후 아래와 같이 GPG Key를 생성한다.

$ gpg --default-new-key-algo rsa4096 --gen-key

사용자명(5글자 이상)과 E-mail을 입력하고, 편의를 위해 Passphrase는 공백으로 둔다.

그리고 GPG Key를 파일로 추출한다. [KEYSPEC]부분에 위에서 입력한 사용자명을 입력한다.

$ gpg -ao public.gpg --export [KEYSPEC]
$ gpg -ao private.gpg --export-secret-key [KEYSPEC]

APT의 배포(Distribution)명과 개인키를 입력한다. (배포명은 stable로 가정한다)

WARNING

개인키는 우측 스크린샷의 PGP signing key pair 항목에 입력하면 된다.
또한 개인키 입력시 -----BEGIN PGP PRIVATE KEY BLOCK----------END PGP PRIVATE KEY BLOCK-----는 제거해야 한다.

클라이언트에서 위의 저장소를 /etc/apt/sources.list파일에 추가한다.

deb https://nexus.local:8080/repository/my/ stable main

WARNING

URL 뒤에 붙는 컴포넌트 들은 /metadata/dists경로 뒤의 Directory 목록을 붙이면 된다.

클라이언트에서 apt에 공개키를 추가한다.

$ apt-key add public.gpg

apt저장소를 업데이트 한다.

$ apt-get -o Acquire::https::nexus.local::Verify-Peer=false update

Troubleshooting

What is "Signing Key"?

새로운 저장소를 생성할 경우 Signing Key항목에 입력해야 할 내용에 대하여 아래와 같이 확인할 수 있다.

1. 플러그인 저장소의 ./src/main/java/net/staticsnow/nexus/repository/apt/internal/gpg/AptSigningFacet.java 파일에서 readSecretKey()함수를 사용하는 부분에서 발생되는 Exception으로 추정된다. 따라서 해당 함수를 확인해 본다:

  private PGPSecretKey readSecretKey() throws IOException, PGPException {
    PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(
        PGPUtil.getDecoderStream(new ByteArrayInputStream(config.keypair.getBytes())),
        new JcaKeyFingerprintCalculator());

    Iterator<PGPSecretKeyRing> keyRings = pgpSec.getKeyRings();
    while (keyRings.hasNext()) {
      PGPSecretKeyRing keyRing = (PGPSecretKeyRing) keyRings.next();

      Iterator<PGPSecretKey> keys = keyRing.getSecretKeys();
      while (keys.hasNext()) {
        PGPSecretKey key = (PGPSecretKey) keys.next();

        if (key.isSigningKey()) {
          return key;
        }
      }
    }

    throw new IllegalStateException("Can't find signing key in key ring.");
  }

2. config.keypair.getBytes()가 기반 정보가 되는 ByteArray를 사용하는 PGPUtil.getDecoderStream함수를 확인해 본다. [https://www.borelly.net/cb/docs/javaBC-1.4.8/pg/org/bouncycastle/openpgp/PGPUtil.html#getDecoderStream(java.io.InputStream PGPUtil.getDecoderStream]

Return either an ArmoredInputStream or a BCPGInputStream based on whether the initial characters of the stream are binary PGP encodings or not.

3. 해당 라이브러리의 구현체를 확인해 본다. Github - bcgit/bc-java 경로는 pg/src/main/java/org/bouncycastle/openpgp/PGPUtil.java 이다.

    /**
     * Obtains a stream that can be used to read PGP data from the provided stream.
     * <p>
     * If the initial bytes of the underlying stream are binary PGP encodings, then the stream will
     * be returned directly, otherwise an {@link ArmoredInputStream} is used to wrap the provided
     * stream and remove ASCII-Armored encoding.
     * </p>
     *
     * @param in the stream to be checked and possibly wrapped.
     * @return a stream that will return PGP binary encoded data.
     * @throws IOException if an error occurs reading the stream, or initialising the
     * {@link ArmoredInputStream}.
     */
    public static InputStream getDecoderStream(
        InputStream in)
        throws IOException
    {
        if (!in.markSupported())
        {
            in = new BufferedInputStreamExt(in);
        }

        in.mark(READ_AHEAD);

        int ch = in.read();


        if ((ch & 0x80) != 0)
        {
            in.reset();

            return in;
        }
        else
        {
            if (!isPossiblyBase64(ch))
            {
                in.reset();

                return new ArmoredInputStream(in);
            }

            byte[] buf = new byte[READ_AHEAD];
            int count = 1;
            int index = 1;

            buf[0] = (byte)ch;
            while (count != READ_AHEAD && (ch = in.read()) >= 0)
            {
                if (!isPossiblyBase64(ch))
                {
                    in.reset();

                    return new ArmoredInputStream(in);
                }

                if (ch != '\n' && ch != '\r')
                {
                    buf[index++] = (byte)ch;
                }

                count++;
            }

            in.reset();

            //
            // nothing but new lines, little else, assume regular armoring
            //
            if (count < 4)
            {
                return new ArmoredInputStream(in);
            }

            //
            // test our non-blank data
            //
            byte[] firstBlock = new byte[8];

            System.arraycopy(buf, 0, firstBlock, 0, firstBlock.length);

            try
            {
                byte[] decoded = Base64.decode(firstBlock);

                //
                // it's a base64 PGP block.
                //
                if ((decoded[0] & 0x80) != 0)
                {
                    return new ArmoredInputStream(in, false);
                }

                return new ArmoredInputStream(in);
            }
            catch (DecoderException e)
            {
                throw new IOException(e.getMessage());
            }
        }
    }

4. 함수 내용 및 업로드시 Exception 메시지를 확인해 보면 Base64 문자열로 Secret Key를 입력하면 된다.

See also

Favorite site