From 66c235624a57634a3c931aa02091c37880ad2204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Sch=C3=A4fer?= Date: Sun, 22 Feb 2026 21:56:11 +0100 Subject: [PATCH 1/5] Add workflow to publish APT repo to S3/CloudFront This workflow automates the process of publishing an APT repository to S3/CloudFront upon release events. It includes steps for configuring AWS credentials, installing necessary tools, processing tags, building packages, and uploading the repository. --- .github/workflows/publish-apt.yml | 201 ++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 .github/workflows/publish-apt.yml diff --git a/.github/workflows/publish-apt.yml b/.github/workflows/publish-apt.yml new file mode 100644 index 0000000..d9797b3 --- /dev/null +++ b/.github/workflows/publish-apt.yml @@ -0,0 +1,201 @@ +name: Publish APT repo to S3/CloudFront + +on: + release: + types: [published] + workflow_dispatch: + inputs: + tag: + description: "Tag to publish (e.g. v1.9.0). Leave empty to use latest release." + required: false + type: string + backfill_all: + description: "Build/publish repo for ALL releases (can take a while)." + required: false + default: false + type: boolean + +permissions: + id-token: write + contents: read + +jobs: + publish-apt: + runs-on: ubuntu-latest + + env: + PKG_NAME: newt + SUITE: stable + COMPONENT: main + REPO_BASE_URL: "https://repo.dev.fosrl.io/apt" + + steps: + - name: Configure AWS credentials (OIDC) + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.AWS_ROLE_ARN }} + aws-region: ${{ vars.AWS_REGION }} + + - name: Install tooling + run: | + set -euo pipefail + sudo apt-get update + sudo apt-get install -y dpkg-dev apt-utils gnupg curl jq gh + + # nfpm + curl -fsSL https://github.com/goreleaser/nfpm/releases/latest/download/nfpm_Linux_x86_64.tar.gz \ + | sudo tar -xz -C /usr/local/bin nfpm + + - name: Determine tags to process + id: tags + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + EVENT_TAG: ${{ github.event.release.tag_name }} + INPUT_TAG: ${{ inputs.tag }} + BACKFILL: ${{ inputs.backfill_all }} + run: | + set -euo pipefail + + if [ "${BACKFILL}" = "true" ]; then + echo "Backfill mode: collecting all release tags..." + TAGS="$(gh release list -R "${REPO}" --limit 200 --json tagName --jq '.[].tagName')" + else + if [ -n "${INPUT_TAG}" ]; then + TAGS="${INPUT_TAG}" + elif [ -n "${EVENT_TAG}" ]; then + TAGS="${EVENT_TAG}" + else + echo "No tag provided; using latest release tag..." + TAGS="$(gh release view -R "${REPO}" --json tagName --jq '.tagName')" + fi + fi + + # Multi-line output for next steps + { + echo "tags<> "${GITHUB_OUTPUT}" + + - name: Import GPG key (APT signing) + env: + APT_GPG_PRIVATE_KEY: ${{ secrets.APT_GPG_PRIVATE_KEY }} + run: | + set -euo pipefail + echo "${APT_GPG_PRIVATE_KEY}" | gpg --batch --import + gpg --list-secret-keys --keyid-format=long + + - name: Pull existing repo from S3 (incremental) + run: | + set -euo pipefail + mkdir -p repo/apt + BUCKET="${{ vars.S3_BUCKET }}" + PREFIX="${{ vars.S3_PREFIX }}" + aws s3 sync "s3://${BUCKET}/${PREFIX}apt/" repo/apt/ || true + + - name: Build packages and update repo (for selected tags) + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + TAGS: ${{ steps.tags.outputs.tags }} + APT_GPG_PASSPHRASE: ${{ secrets.APT_GPG_PASSPHRASE }} + run: | + set -euo pipefail + + KEYID="$(gpg --list-secret-keys --with-colons | awk -F: '$1=="sec"{print $5; exit}')" + test -n "${KEYID}" + + mkdir -p assets build + + # Iterate over tags safely (no bash <<< with GH expressions) + printf '%s\n' "${TAGS}" | while IFS= read -r TAG; do + [ -z "${TAG}" ] && continue + echo "=== Processing tag: ${TAG} ===" + + rm -rf assets build + mkdir -p assets build + + gh release download "${TAG}" -R "${REPO}" -p "newt_linux_amd64" -D assets + gh release download "${TAG}" -R "${REPO}" -p "newt_linux_arm64" -D assets + + VERSION="${TAG#v}" + + for arch in amd64 arm64; do + bin="assets/newt_linux_${arch}" + test -f "${bin}" + install -Dm755 "${bin}" "build/newt" + + cat > nfpm.yaml <<'EOF' +name: newt +arch: ARCH_PLACEHOLDER +platform: linux +version: VERSION_PLACEHOLDER +section: net +priority: optional +maintainer: fosrl +description: Newt - userspace tunnel client and TCP/UDP proxy +contents: + - src: build/newt + dst: /usr/bin/newt +EOF + + # Replace placeholders (avoid YAML/heredoc expression issues) + sed -i "s/ARCH_PLACEHOLDER/${arch}/" nfpm.yaml + sed -i "s/VERSION_PLACEHOLDER/${VERSION}/" nfpm.yaml + + nfpm package -p deb -f nfpm.yaml -t "build/${PKG_NAME}_${VERSION}_${arch}.deb" + done + + mkdir -p "repo/apt/pool/${COMPONENT}/${PKG_NAME:0:1}/${PKG_NAME}/" + cp -v build/*.deb "repo/apt/pool/${COMPONENT}/${PKG_NAME:0:1}/${PKG_NAME}/" + done + + cd repo/apt + + for arch in amd64 arm64; do + mkdir -p "dists/${SUITE}/${COMPONENT}/binary-${arch}" + dpkg-scanpackages -a "${arch}" pool > "dists/${SUITE}/${COMPONENT}/binary-${arch}/Packages" + gzip -fk "dists/${SUITE}/${COMPONENT}/binary-${arch}/Packages" + done + + cat > apt-ftparchive.conf < "dists/${SUITE}/Release" + + cd "dists/${SUITE}" + + gpg --batch --yes --pinentry-mode loopback \ + ${APT_GPG_PASSPHRASE:+--passphrase "${APT_GPG_PASSPHRASE}"} \ + --local-user "${KEYID}" \ + --clearsign -o InRelease Release + + gpg --batch --yes --pinentry-mode loopback \ + ${APT_GPG_PASSPHRASE:+--passphrase "${APT_GPG_PASSPHRASE}"} \ + --local-user "${KEYID}" \ + -abs -o Release.gpg Release + + cd ../../.. + gpg --batch --yes --armor --export "${KEYID}" > public.key + + - name: Upload repo to S3 + run: | + set -euo pipefail + BUCKET="${{ vars.S3_BUCKET }}" + PREFIX="${{ vars.S3_PREFIX }}" + aws s3 sync repo/apt "s3://${BUCKET}/${PREFIX}apt/" --delete + + - name: CloudFront invalidation (metadata only) + run: | + set -euo pipefail + aws cloudfront create-invalidation \ + --distribution-id "${{ vars.CLOUDFRONT_DISTRIBUTION_ID }}" \ + --paths "/${{ vars.S3_PREFIX }}apt/dists/*" "/${{ vars.S3_PREFIX }}apt/public.key" From 18556f34b2f4f3259fa81f0fca02cc92d9ca02d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Sch=C3=A4fer?= Date: Sun, 22 Feb 2026 21:58:56 +0100 Subject: [PATCH 2/5] Refactor package build process in publish-apt.yml Refactor nfpm.yaml generation to use Python script and update package naming conventions. --- .github/workflows/publish-apt.yml | 51 +++++++++++++------------------ 1 file changed, 21 insertions(+), 30 deletions(-) diff --git a/.github/workflows/publish-apt.yml b/.github/workflows/publish-apt.yml index d9797b3..33bc01b 100644 --- a/.github/workflows/publish-apt.yml +++ b/.github/workflows/publish-apt.yml @@ -94,7 +94,7 @@ jobs: PREFIX="${{ vars.S3_PREFIX }}" aws s3 sync "s3://${BUCKET}/${PREFIX}apt/" repo/apt/ || true - - name: Build packages and update repo (for selected tags) + - name: Build packages and update repo (for selected tags) env: GH_TOKEN: ${{ github.token }} REPO: ${{ github.repository }} @@ -108,7 +108,6 @@ jobs: mkdir -p assets build - # Iterate over tags safely (no bash <<< with GH expressions) printf '%s\n' "${TAGS}" | while IFS= read -r TAG; do [ -z "${TAG}" ] && continue echo "=== Processing tag: ${TAG} ===" @@ -126,37 +125,29 @@ jobs: test -f "${bin}" install -Dm755 "${bin}" "build/newt" - cat > nfpm.yaml <<'EOF' -name: newt -arch: ARCH_PLACEHOLDER -platform: linux -version: VERSION_PLACEHOLDER -section: net -priority: optional -maintainer: fosrl -description: Newt - userspace tunnel client and TCP/UDP proxy -contents: - - src: build/newt - dst: /usr/bin/newt -EOF + python3 - < nfpm.yaml - # Replace placeholders (avoid YAML/heredoc expression issues) - sed -i "s/ARCH_PLACEHOLDER/${arch}/" nfpm.yaml - sed -i "s/VERSION_PLACEHOLDER/${VERSION}/" nfpm.yaml - - nfpm package -p deb -f nfpm.yaml -t "build/${PKG_NAME}_${VERSION}_${arch}.deb" + nfpm package -p deb -f nfpm.yaml -t "build/newt_${VERSION}_${arch}.deb" done - mkdir -p "repo/apt/pool/${COMPONENT}/${PKG_NAME:0:1}/${PKG_NAME}/" - cp -v build/*.deb "repo/apt/pool/${COMPONENT}/${PKG_NAME:0:1}/${PKG_NAME}/" - done - - cd repo/apt - - for arch in amd64 arm64; do - mkdir -p "dists/${SUITE}/${COMPONENT}/binary-${arch}" - dpkg-scanpackages -a "${arch}" pool > "dists/${SUITE}/${COMPONENT}/binary-${arch}/Packages" - gzip -fk "dists/${SUITE}/${COMPONENT}/binary-${arch}/Packages" + mkdir -p "repo/apt/pool/main/n/newt/" + cp -v build/*.deb "repo/apt/pool/main/n/newt/" done cat > apt-ftparchive.conf < Date: Sun, 22 Feb 2026 22:00:46 +0100 Subject: [PATCH 3/5] Update APT publishing workflow configuration Refactor APT publishing workflow with improved variable handling and script execution. --- .github/workflows/publish-apt.yml | 174 ++++-------------------------- 1 file changed, 22 insertions(+), 152 deletions(-) diff --git a/.github/workflows/publish-apt.yml b/.github/workflows/publish-apt.yml index 33bc01b..7726218 100644 --- a/.github/workflows/publish-apt.yml +++ b/.github/workflows/publish-apt.yml @@ -10,7 +10,7 @@ on: required: false type: string backfill_all: - description: "Build/publish repo for ALL releases (can take a while)." + description: "Build/publish repo for ALL releases." required: false default: false type: boolean @@ -20,173 +20,43 @@ permissions: contents: read jobs: - publish-apt: + publish: runs-on: ubuntu-latest - env: PKG_NAME: newt SUITE: stable COMPONENT: main - REPO_BASE_URL: "https://repo.dev.fosrl.io/apt" + REPO_BASE_URL: https://repo.dev.fosrl.io/apt + + AWS_REGION: ${{ vars.AWS_REGION }} + S3_BUCKET: ${{ vars.S3_BUCKET }} + S3_PREFIX: ${{ vars.S3_PREFIX }} + CLOUDFRONT_DISTRIBUTION_ID: ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }} + + INPUT_TAG: ${{ inputs.tag }} + BACKFILL_ALL: ${{ inputs.backfill_all }} + EVENT_TAG: ${{ github.event.release.tag_name }} + GH_REPO: ${{ github.repository }} steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Configure AWS credentials (OIDC) uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_ARN }} aws-region: ${{ vars.AWS_REGION }} - - name: Install tooling - run: | - set -euo pipefail - sudo apt-get update - sudo apt-get install -y dpkg-dev apt-utils gnupg curl jq gh + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y dpkg-dev apt-utils gnupg curl jq gh - # nfpm - curl -fsSL https://github.com/goreleaser/nfpm/releases/latest/download/nfpm_Linux_x86_64.tar.gz \ - | sudo tar -xz -C /usr/local/bin nfpm + - name: Install nfpm + run: curl -fsSL https://github.com/goreleaser/nfpm/releases/latest/download/nfpm_Linux_x86_64.tar.gz | sudo tar -xz -C /usr/local/bin nfpm - - name: Determine tags to process - id: tags + - name: Publish APT repo env: GH_TOKEN: ${{ github.token }} - REPO: ${{ github.repository }} - EVENT_TAG: ${{ github.event.release.tag_name }} - INPUT_TAG: ${{ inputs.tag }} - BACKFILL: ${{ inputs.backfill_all }} - run: | - set -euo pipefail - - if [ "${BACKFILL}" = "true" ]; then - echo "Backfill mode: collecting all release tags..." - TAGS="$(gh release list -R "${REPO}" --limit 200 --json tagName --jq '.[].tagName')" - else - if [ -n "${INPUT_TAG}" ]; then - TAGS="${INPUT_TAG}" - elif [ -n "${EVENT_TAG}" ]; then - TAGS="${EVENT_TAG}" - else - echo "No tag provided; using latest release tag..." - TAGS="$(gh release view -R "${REPO}" --json tagName --jq '.tagName')" - fi - fi - - # Multi-line output for next steps - { - echo "tags<> "${GITHUB_OUTPUT}" - - - name: Import GPG key (APT signing) - env: APT_GPG_PRIVATE_KEY: ${{ secrets.APT_GPG_PRIVATE_KEY }} - run: | - set -euo pipefail - echo "${APT_GPG_PRIVATE_KEY}" | gpg --batch --import - gpg --list-secret-keys --keyid-format=long - - - name: Pull existing repo from S3 (incremental) - run: | - set -euo pipefail - mkdir -p repo/apt - BUCKET="${{ vars.S3_BUCKET }}" - PREFIX="${{ vars.S3_PREFIX }}" - aws s3 sync "s3://${BUCKET}/${PREFIX}apt/" repo/apt/ || true - - - name: Build packages and update repo (for selected tags) - env: - GH_TOKEN: ${{ github.token }} - REPO: ${{ github.repository }} - TAGS: ${{ steps.tags.outputs.tags }} APT_GPG_PASSPHRASE: ${{ secrets.APT_GPG_PASSPHRASE }} - run: | - set -euo pipefail - - KEYID="$(gpg --list-secret-keys --with-colons | awk -F: '$1=="sec"{print $5; exit}')" - test -n "${KEYID}" - - mkdir -p assets build - - printf '%s\n' "${TAGS}" | while IFS= read -r TAG; do - [ -z "${TAG}" ] && continue - echo "=== Processing tag: ${TAG} ===" - - rm -rf assets build - mkdir -p assets build - - gh release download "${TAG}" -R "${REPO}" -p "newt_linux_amd64" -D assets - gh release download "${TAG}" -R "${REPO}" -p "newt_linux_arm64" -D assets - - VERSION="${TAG#v}" - - for arch in amd64 arm64; do - bin="assets/newt_linux_${arch}" - test -f "${bin}" - install -Dm755 "${bin}" "build/newt" - - python3 - < nfpm.yaml - - nfpm package -p deb -f nfpm.yaml -t "build/newt_${VERSION}_${arch}.deb" - done - - mkdir -p "repo/apt/pool/main/n/newt/" - cp -v build/*.deb "repo/apt/pool/main/n/newt/" - done - - cat > apt-ftparchive.conf < "dists/${SUITE}/Release" - - cd "dists/${SUITE}" - - gpg --batch --yes --pinentry-mode loopback \ - ${APT_GPG_PASSPHRASE:+--passphrase "${APT_GPG_PASSPHRASE}"} \ - --local-user "${KEYID}" \ - --clearsign -o InRelease Release - - gpg --batch --yes --pinentry-mode loopback \ - ${APT_GPG_PASSPHRASE:+--passphrase "${APT_GPG_PASSPHRASE}"} \ - --local-user "${KEYID}" \ - -abs -o Release.gpg Release - - cd ../../.. - gpg --batch --yes --armor --export "${KEYID}" > public.key - - - name: Upload repo to S3 - run: | - set -euo pipefail - BUCKET="${{ vars.S3_BUCKET }}" - PREFIX="${{ vars.S3_PREFIX }}" - aws s3 sync repo/apt "s3://${BUCKET}/${PREFIX}apt/" --delete - - - name: CloudFront invalidation (metadata only) - run: | - set -euo pipefail - aws cloudfront create-invalidation \ - --distribution-id "${{ vars.CLOUDFRONT_DISTRIBUTION_ID }}" \ - --paths "/${{ vars.S3_PREFIX }}apt/dists/*" "/${{ vars.S3_PREFIX }}apt/public.key" + run: ./scripts/publish-apt.sh From 3389088c43ff024e0a4b6571ef699b448ea2f8ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Sch=C3=A4fer?= Date: Sun, 22 Feb 2026 22:01:24 +0100 Subject: [PATCH 4/5] Add script to publish APT packages to S3 This script publishes APT packages to an S3 bucket, handling GPG signing and CloudFront invalidation. --- scripts/publish-apt.sh | 139 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 scripts/publish-apt.sh diff --git a/scripts/publish-apt.sh b/scripts/publish-apt.sh new file mode 100644 index 0000000..eba6315 --- /dev/null +++ b/scripts/publish-apt.sh @@ -0,0 +1,139 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ---- required env ---- +: "${GH_REPO:?}" +: "${S3_BUCKET:?}" +: "${AWS_REGION:?}" +: "${CLOUDFRONT_DISTRIBUTION_ID:?}" +: "${PKG_NAME:?}" +: "${SUITE:?}" +: "${COMPONENT:?}" +: "${APT_GPG_PRIVATE_KEY:?}" + +S3_PREFIX="${S3_PREFIX:-}" +if [[ -n "${S3_PREFIX}" && "${S3_PREFIX}" != */ ]]; then + S3_PREFIX="${S3_PREFIX}/" +fi + +WORKDIR="$(pwd)" +mkdir -p repo/apt assets build + +echo "${APT_GPG_PRIVATE_KEY}" | gpg --batch --import >/dev/null 2>&1 || true + +KEYID="$(gpg --list-secret-keys --with-colons | awk -F: '$1=="sec"{print $5; exit}')" +if [[ -z "${KEYID}" ]]; then + echo "ERROR: No GPG secret key available after import." + exit 1 +fi + +# Determine which tags to process +TAGS="" +if [[ "${BACKFILL_ALL:-false}" == "true" ]]; then + echo "Backfill mode: collecting all release tags..." + TAGS="$(gh release list -R "${GH_REPO}" --limit 200 --json tagName --jq '.[].tagName')" +else + if [[ -n "${INPUT_TAG:-}" ]]; then + TAGS="${INPUT_TAG}" + elif [[ -n "${EVENT_TAG:-}" ]]; then + TAGS="${EVENT_TAG}" + else + echo "No tag provided; using latest release tag..." + TAGS="$(gh release view -R "${GH_REPO}" --json tagName --jq '.tagName')" + fi +fi + +echo "Tags to process:" +printf '%s\n' "${TAGS}" + +# Pull existing repo from S3 so we keep older versions +echo "Sync existing repo from S3..." +aws s3 sync "s3://${S3_BUCKET}/${S3_PREFIX}apt/" repo/apt/ >/dev/null 2>&1 || true + +# Build and add packages +while IFS= read -r TAG; do + [[ -z "${TAG}" ]] && continue + echo "=== Processing tag: ${TAG} ===" + + rm -rf assets build + mkdir -p assets build + + gh release download "${TAG}" -R "${GH_REPO}" -p "newt_linux_amd64" -D assets + gh release download "${TAG}" -R "${GH_REPO}" -p "newt_linux_arm64" -D assets + + VERSION="${TAG#v}" + + for arch in amd64 arm64; do + bin="assets/newt_linux_${arch}" + if [[ ! -f "${bin}" ]]; then + echo "ERROR: Missing release asset: ${bin}" + exit 1 + fi + + install -Dm755 "${bin}" "build/newt" + + # Create nfpm config from template file (no heredoc here) + sed \ + -e "s/__PKG_NAME__/${PKG_NAME}/g" \ + -e "s/__ARCH__/${arch}/g" \ + -e "s/__VERSION__/${VERSION}/g" \ + scripts/nfpm.yaml.tmpl > nfpm.yaml + + nfpm package -p deb -f nfpm.yaml -t "build/${PKG_NAME}_${VERSION}_${arch}.deb" + done + + mkdir -p "repo/apt/pool/${COMPONENT}/${PKG_NAME:0:1}/${PKG_NAME}/" + cp -v build/*.deb "repo/apt/pool/${COMPONENT}/${PKG_NAME:0:1}/${PKG_NAME}/" + +done <<< "${TAGS}" + +# Regenerate metadata +cd repo/apt + +for arch in amd64 arm64; do + mkdir -p "dists/${SUITE}/${COMPONENT}/binary-${arch}" + dpkg-scanpackages -a "${arch}" pool > "dists/${SUITE}/${COMPONENT}/binary-${arch}/Packages" + gzip -fk "dists/${SUITE}/${COMPONENT}/binary-${arch}/Packages" +done + +# Release file with hashes +cat > apt-ftparchive.conf < "dists/${SUITE}/Release" + +# Sign Release +cd "dists/${SUITE}" + +gpg --batch --yes --pinentry-mode loopback \ + ${APT_GPG_PASSPHRASE:+--passphrase "${APT_GPG_PASSPHRASE}"} \ + --local-user "${KEYID}" \ + --clearsign -o InRelease Release + +gpg --batch --yes --pinentry-mode loopback \ + ${APT_GPG_PASSPHRASE:+--passphrase "${APT_GPG_PASSPHRASE}"} \ + --local-user "${KEYID}" \ + -abs -o Release.gpg Release + +# Export public key into apt repo root +cd ../../.. +gpg --batch --yes --armor --export "${KEYID}" > public.key + +# Upload to S3 +echo "Uploading to S3..." +aws s3 sync "${WORKDIR}/repo/apt" "s3://${S3_BUCKET}/${S3_PREFIX}apt/" --delete + +# Invalidate metadata +echo "CloudFront invalidation..." +aws cloudfront create-invalidation \ + --distribution-id "${CLOUDFRONT_DISTRIBUTION_ID}" \ + --paths "/${S3_PREFIX}apt/dists/*" "/${S3_PREFIX}apt/public.key" + +echo "Done. Repo base: ${REPO_BASE_URL}" From 57afe91e85b150ff74d7c0fa92e30f9ac34761ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Sch=C3=A4fer?= Date: Sun, 22 Feb 2026 22:02:04 +0100 Subject: [PATCH 5/5] Create nfpm.yaml.tmpl for Newt packaging Added nfpm.yaml template for packaging configuration. --- scripts/nfpm.yaml.tmpl | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 scripts/nfpm.yaml.tmpl diff --git a/scripts/nfpm.yaml.tmpl b/scripts/nfpm.yaml.tmpl new file mode 100644 index 0000000..f9a7d60 --- /dev/null +++ b/scripts/nfpm.yaml.tmpl @@ -0,0 +1,11 @@ +name: __PKG_NAME__ +arch: __ARCH__ +platform: linux +version: __VERSION__ +section: net +priority: optional +maintainer: fosrl +description: Newt - userspace tunnel client and TCP/UDP proxy +contents: + - src: build/newt + dst: /usr/bin/newt