Added local tool aptfile to use with apt based development installation. (#772)

This commit is contained in:
Steve Karg
2024-09-21 09:26:34 -05:00
committed by GitHub
parent 3d86873346
commit fe1fd39261
7 changed files with 596 additions and 0 deletions
+6
View File
@@ -1,6 +1,11 @@
#!/usr/bin/env aptfile
# ^ note the above shebang
# https://github.com/seatgeek/bash-aptfile
# Copied locally into tools/bash-aptfile
# This file is used to install packages on a development environment
# using the apt-get package manager.
# Run using the following command:
# sudo ./tools/bash-aptfile/bin/aptfile ./aptfile
# trigger an apt-get update
update
@@ -19,6 +24,7 @@ package "mingw-w64"
package "git"
package "gitk"
package "gitg"
package "pre-commit"
# install source code format packages
package "tofrodos"
+11
View File
@@ -0,0 +1,11 @@
root = true
[*]
insert_final_newline = true
indent_style = space
indent_size = 2
[Makefile]
insert_final_newline = true
indent_style = tab
indent_size = 4
+48
View File
@@ -0,0 +1,48 @@
# This dockerfile is used to build debian packages
# it should not be invoked directly.
# To build a debian package, run:
#
# make deb
#
# The debian package will be copied into the working
# directory. You can change the version by modifying
# the version in the Makefile.
#
FROM ubuntu:14.04
RUN apt-get -qq update
RUN \
export DEBIAN_FRONTEND=noninteractive && \
apt-get -qq install -qq -y ruby ruby-dev ruby-bundler > /dev/null && \
apt-get -qq install -qq -y build-essential rpm > /dev/null && \
rm -rf /var/lib/apt/lists/*
RUN gem install fpm -q > /dev/null
WORKDIR /data
RUN mkdir -p /data/build/usr/local/bin /data/build/var/lib/aptfile
COPY bin/aptfile /data/build/usr/local/bin/aptfile
RUN echo "VERSION" > /data/build/var/lib/aptfile/VERSION \
&& chmod +x /data/build/usr/local/bin/aptfile
RUN fpm --log warn \
-s dir \
-t deb \
-C /data/build \
--name aptfile \
--version "VERSION" \
--description "a simple method of defining apt-get dependencies for an application" \
--maintainer "SeatGeek <hi@seatgeek.com>" \
--vendor "SeatGeek" \
--license "BSD 3-Clause" \
--url "https://github.com/seatgeek/bash-aptfile" \
--deb-no-default-config-files \
.
RUN dpkg -i /data/aptfile_"VERSION"_amd64.deb && \
dpkg -s aptfile && \
aptfile -v
+12
View File
@@ -0,0 +1,12 @@
Copyright (c) 2015, SeatGeek, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+30
View File
@@ -0,0 +1,30 @@
VERSION = 1.1.0
DOCKER_IMAGE = aptfile-$(VERSION)
shellcheck:
ifeq ($(shell shellcheck > /dev/null 2>&1 ; echo $$?),127)
ifeq ($(shell uname),Darwin)
brew install shellcheck
else
sudo add-apt-repository 'deb http://archive.ubuntu.com/ubuntu trusty-backports main restricted universe multiverse'
sudo apt-get update -qq && sudo apt-get install -qq -y shellcheck
endif
endif
lint: shellcheck
shellcheck bin/aptfile
clean:
rm -f *.deb
deb: clean
sed -i -e 's/"VERSION"/$(VERSION)/' Dockerfile && rm Dockerfile-e
docker build -t $(DOCKER_IMAGE) .
bash -c 'ID=$$(docker run -i -a stdin $(DOCKER_IMAGE)) && docker cp $$ID:/data/aptfile_$(VERSION)_amd64.deb . && docker rm $$ID'
git checkout -- Dockerfile
release:
@git status | grep -q "working directory clean" || (echo "You have uncomitted changes" && exit 1)
$(MAKE) deb
.PHONY: shellcheck lint clean deb release
+197
View File
@@ -0,0 +1,197 @@
# bash-aptfile
a simple method of defining apt-get dependencies for an application
## requirements
- apt-get
- dpkg
## installation
```shell
# curl all the things!
curl -o /usr/local/bin/aptfile https://raw.githubusercontent.com/seatgeek/bash-aptfile/master/bin/aptfile
chmod +x /usr/local/bin/aptfile
```
You can *also* create a debian package via docker using `make` with the provided `Dockerfile`. The debian package is made via the wonders of `fpm`:
```shell
git clone https://github.com/seatgeek/bash-aptfile.git
cd bash-aptfile
make deb
# sudo all the things!
sudo dpkg -i *.deb
```
## usage
Create an `aptfile` in the base of your project:
```shell
#!/usr/bin/env aptfile
# ^ note the above shebang
# trigger an apt-get update
update
# install some packages
package "build-essential"
package "git-core"
package "software-properties-common"
# install a ppa
ppa "fkrull/deadsnakes-python2.7"
# install a few more packages from that ppa
package "python2.7"
package "python-pip"
package "python-dev"
# setup some debian configuration
debconf_selection "mysql mysql-server/root_password password root"
debconf_selection "mysql mysql-server/root_password_again password root"
# install another package
package "mysql-server"
# install a package from a download url
package_from_url "google-chrome-stable" "https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb"
# you can also execute arbitrary bash
echo "🚀 ALL GOOD TO GO"
```
Now you can run it:
```shell
# aptfile *does not* use sudo by default!
sudo aptfile
# enable bash tracing
TRACE=1 sudo -E aptfile
# you can also execute a specific aptfile
sudo aptfile path/to/your/aptfile
# or make the file executable and run it directly
chmod +x path/to/your/aptfile
sudo ./aptfile
```
And you'll see the following lovely output:
```
Running update
[NEW] package build-essential
[NEW] package git-core
[NEW] package software-properties-common
[NEW] ppa fkrull/deadsnakes-python2.7
[NEW] package python2.7
[NEW] package python-pip
[NEW] package python-dev
[OK] set debconf line: mysql mysql-server/root_password password root
[OK] set debconf line: mysql mysql-server/root_password_again password root
[NEW] package mysql-server
[NEW] package google-chrome-stable
🚀 ALL GOOD TO GO
```
Note that `aptfile` runs uses `--force-confnew` - it will forcibly use the package's version of a conf file if a conflict is found.
## aptfile primitives
You can use any of the following primitives when creating your service's aptfile:
### update
Runs apt-get update:
```shell
update
```
### package
Installs a single package:
```shell
package "git-core"
```
### package_from_url
Installs a single package from a url:
```shell
package_from_url "google-chrome-stable" "https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb"
```
### packagelist
Installs a multiple packages in one apt call:
```shell
packagelist "git-core" "gitsome"
```
### repository
Installs an aptitude repository via `add-apt-repository`:
```shell
repository "deb http://us.archive.ubuntu.com/ubuntu/ saucy universe multiverse"
repository "ppa:mozillateam/firefox-next"
```
### repository_file
Installs an aptitude repository in `/etc/apt/sources.list.d`:
```shell
# Add the exact line to /etc/apt/sources.list.d/google-chrome.list
repository_file "google-chrome" "deb http://dl.google.com/linux/chrome/deb/ stable main"
# without 'deb', suite (defaults to lsb_release -sc) and components (defaults to 'main') are added
# All three lines do the same (on xenial)
repository_file "git-lfs.list" "deb https://packagecloud.io/github/git-lfs/ubuntu/ xenial main"
repository_file "git-lfs.list" "https://packagecloud.io/github/git-lfs/ubuntu/ main"
repository_file "git-lfs.list" "https://packagecloud.io/github/git-lfs/ubuntu/"
```
### ppa
The preferred method for installing a ppa as it properly handles not re-running `add-apt-repository`:
```shell
ppa "mozillateam/firefox-next"
```
### debconf_selection
Allows you to set a debconf selection:
```shell
debconf_selection "mysql mysql-server/root_password password root"
```
## helper functions
These helper functions can be used inside your custom aptfile.
### log_fail
Logs a message to standard error and exits. If this is called, the full output from the dpkg calls will be output as well for further inspection.
```shell
log_fail "Unable to find the proper package version"
```
### log_info
Outputs a message to stdout.
```shell
log_info "🚀 ALL GOOD TO GO"
```
+292
View File
@@ -0,0 +1,292 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $TRACE ]] && set -x && export TRACE=$TRACE
version() {
local VERSION="dev-master"
if [[ -f /var/lib/aptfile/VERSION ]]; then
VERSION=$(cat /var/lib/aptfile/VERSION)
fi
echo "aptfile $VERSION"
}
usage() {
version
echo "Usage: aptfile <aptfile>"
}
help() {
usage
echo
echo " <aptfile> is the path to a bash file with apt instructions."
echo
echo " -h, --help Display this help message"
echo " -v, --version Display the version number"
echo
echo " For more information, see https://github.com/seatgeek/bash-aptfile"
echo
}
resolve_link() {
$(type -p greadlink readlink | head -1) "$1"
}
abs_dirname() {
local cwd
local path="$1"
cwd="$(pwd)"
while [ -n "$path" ]; do
cd "${path%/*}"
local name="${path##*/}"
path="$(resolve_link "$name" || true)"
done
pwd
cd "$cwd"
}
expand_path() {
{
cd "$(dirname "$1")" 2>/dev/null
local dirname="$PWD"
cd "$OLDPWD"
echo "$dirname/$(basename "$1")"
} || echo "$1"
}
options=()
arguments=()
for arg in "$@"; do
if [ "${arg:0:1}" = "-" ]; then
if [ "${arg:1:1}" = "-" ]; then
options[${#options[*]}]="${arg:2}"
else
index=1
while option="${arg:$index:1}"; do
[ -n "$option" ] || break
options[${#options[*]}]="$option"
let index+=1
done
fi
else
arguments[${#arguments[*]}]="$arg"
fi
done
for option in "${options[@]}"; do
case "$option" in
"h" | "help")
help
exit 0
;;
"v" | "version")
version
exit 0
;;
*)
usage >&2
exit 1
;;
esac
done
if [ "${#arguments[@]}" -eq 0 ]; then
APTFILE_PATH=$(expand_path "aptfile")
if [[ ! -f $APTFILE_PATH ]]; then
usage >&2
exit 1
fi
set -- "aptfile" "$@"
fi
export APTFILE_COLOR_OFF="\033[0m" # unsets color to term fg color
export APTFILE_RED="\033[0;31m" # red
export APTFILE_GREEN="\033[0;32m" # green
export APTFILE_YELLOW="\033[0;33m" # yellow
export APTFILE_MAGENTA="\033[0;35m" # magenta
export APTFILE_CYAN="\033[0;36m" # cyan
logfile=$(basename "$0")
TMP_APTFILE_LOGFILE=$(mktemp "/tmp/${logfile}.XXXXXX") || {
log_fail "${APTFILE_RED}WARNING: Cannot create temp file using mktemp in /tmp dir ${APTFILE_COLOR_OFF}\n"
}
export TMP_APTFILE_LOGFILE="$TMP_APTFILE_LOGFILE"
trap 'rm -rf "$TMP_APTFILE_LOGFILE" > /dev/null' INT TERM EXIT
log_fail() {
[[ $TRACE ]] && set -x
echo -e "${APTFILE_RED}$*${APTFILE_COLOR_OFF}" 1>&2
[[ -f "$TMP_APTFILE_LOGFILE" ]] && echo -e "verbose logs:\n" 1>&2 && sed -e 's/^/ /' "$TMP_APTFILE_LOGFILE"
exit 1
}
log_info() {
[[ $TRACE ]] && set -x
echo -e "$@"
}
update() {
[[ $TRACE ]] && set -x
log_info "Running update"
apt-get update >"$TMP_APTFILE_LOGFILE" 2>&1
[[ $? -eq 0 ]] || log_fail "Failed to run update"
}
package() {
[[ $TRACE ]] && set -x
[[ -z $1 ]] && log_fail "Please specify a package to install"
local pkg="$1"
dpkg --force-confnew -s "$pkg" >"$TMP_APTFILE_LOGFILE" 2>&1 && log_info "${APTFILE_CYAN}[OK]${APTFILE_COLOR_OFF} package $pkg" && return 0
apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confnew" -qq -y install "$pkg"
[[ $? -eq 0 ]] || log_fail "${APTFILE_RED}[FAIL]${APTFILE_COLOR_OFF} package $pkg"
log_info "${APTFILE_GREEN}[NEW]${APTFILE_COLOR_OFF} package $pkg"
}
package_from_url() {
[[ $TRACE ]] && set -x
[[ -z $2 ]] && log_fail "Please specify a name and a download url to install the package from"
local name=$1
local url=$2
if type curl >/dev/null 2>&1; then
local dl_cmd="curl"
local dl_options="-so"
elif type wget >/dev/null 2>&1; then
local dl_cmd="wget"
local dl_options="-qO"
else
log_fail "Neither curl nor wget found. Unable to download $url"
fi
dpkg --force-confnew -s "$name" >"$TMP_APTFILE_LOGFILE" 2>&1 && log_info "${APTFILE_CYAN}[OK]${APTFILE_COLOR_OFF} package $name" && return 0
tempdir=$(mktemp -d)
$dl_cmd $dl_options $tempdir/${name}.deb $url \
&& apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confnew" -qq -y install "$tempdir/${name}.deb"
if [[ $? -ne 0 ]]; then
rm -r $tempdir
log_fail "${APTFILE_RED}[FAIL]${APTFILE_COLOR_OFF} package $name"
fi
rm -r $tempdir
log_info "${APTFILE_GREEN}[NEW]${APTFILE_COLOR_OFF} package $name"
}
packagelist() {
[[ $TRACE ]] && set -x
[[ -z $1 ]] && log_fail "Please specify at least one package to install"
local input_packages=$@
local install_packages=()
for pkg in $input_packages; do
dpkg --force-confnew -s "$pkg" >"$TMP_APTFILE_LOGFILE" 2>&1 && log_info "${APTFILE_CYAN}[OK]${APTFILE_COLOR_OFF} package $pkg" && continue
install_packages+=($pkg)
done
if [[ ${#install_packages[@]} -gt 0 ]]; then
apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confnew" -qq -y install ${install_packages[@]}
[[ $? -eq 0 ]] || log_fail "${APTFILE_RED}[FAIL]${APTFILE_COLOR_OFF} packages ${install_packages[@]}"
log_info "${APTFILE_GREEN}[NEW]${APTFILE_COLOR_OFF} packages ${install_packages[@]}"
fi
}
ppa() {
[[ $TRACE ]] && set -x
[[ -z $1 ]] && log_fail "Please specify a repository to setup"
local repo="$1"
if [[ -d /etc/apt/sources.list.d/ ]]; then
grep ^ /etc/apt/sources.list /etc/apt/sources.list.d/* | grep -q "$repo" && log_info "${APTFILE_CYAN}[OK]${APTFILE_COLOR_OFF} ppa $repo" && return 0
fi
repository "ppa:$1"
}
repository() {
[[ $TRACE ]] && set -x
[[ -z $1 ]] && log_fail "Please specify a repository to setup"
local repo="$1"
if [[ -d /etc/apt/sources.list.d/ ]]; then
grep ^ /etc/apt/sources.list /etc/apt/sources.list.d/* | grep -Fq "$repo" && log_info "${APTFILE_CYAN}[OK]${APTFILE_COLOR_OFF} repository $repo" && return 0
fi
add-apt-repository -y "$repo" >"$TMP_APTFILE_LOGFILE" 2>&1
[[ $? -eq 0 ]] || log_fail "${APTFILE_RED}[FAIL]${APTFILE_COLOR_OFF} repository $pkg"
update
log_info "${APTFILE_GREEN}[NEW]${APTFILE_COLOR_OFF} repository $repo"
}
repository_file() {
[[ $TRACE ]] && set -x
[[ -z $2 ]] && log_fail "Please specify a filename and sourceline to setup"
local repofile="$1"
local repo="$2"
# sourceline is not a complete repo configuration, needs modifying
# i.e. not sourceline="deb http://domain.invalid/debian buster main extra"
if [[ "$repo" != "deb "* ]]; then
releasename=$(lsb_release -sc)
if [[ "$repo" == *" "* ]]; then
# Components given in sourceline, adding suite
# i.e. sourceline="http://domain.invalid/debian main"
repo="deb ${repo/ / $releasename }"
else
# only URL given, adding suite and component
# i.e. sourceline="http://domain.invalid/debian"
repo="deb ${repo} $releasename main"
fi
fi
if [[ "$repofile" != *.list ]]; then
# Adding extension to enable parsing file
repofile=${repofile}.list
fi
# Adding path
repofile="/etc/apt/sources.list.d/$repofile"
[[ -d "/etc/apt/sources.list.d" ]] || mkdir -p /etc/apt/sources.list.d
grep ^ /etc/apt/sources.list /etc/apt/sources.list.d/* | grep -Fq "$repo" && log_info "${APTFILE_CYAN}[OK]${APTFILE_COLOR_OFF} repository $repo" && return 0
echo "Writing '$repo' to file '$repofile'" >"$TMP_APTFILE_LOGFILE"
echo "$repo" >"$repofile" 2>>"$TMP_APTFILE_LOGFILE"
[[ $? -eq 0 ]] || log_fail "${APTFILE_RED}[FAIL]${APTFILE_COLOR_OFF} repository $pkg"
update
log_info "${APTFILE_GREEN}[NEW]${APTFILE_COLOR_OFF} repository $repo"
}
debconf_selection() {
[[ $TRACE ]] && set -x
[[ -z $1 ]] && log_fail "Please specify a debconf line"
echo "$1" | debconf-set-selections
[[ $? -eq 0 ]] || log_fail "${APTFILE_RED}[FAIL]${APTFILE_COLOR_OFF} debconf: $1"
log_info "${APTFILE_CYAN}[OK]${APTFILE_COLOR_OFF} set debconf line: $1"
}
if [ -z "$TMPDIR" ]; then
APTFILE_TMPDIR="/tmp"
else
APTFILE_TMPDIR="${TMPDIR%/}"
fi
APTFILE_INPUT=$(expand_path "$1")
APTFILE_TMPNAME="$APTFILE_TMPDIR/aptfile.$$"
APTFILE_OUTPUT="${APTFILE_TMPNAME}.out"
aptfile_preprocess_source() {
tail -n +2 "$1" >"$APTFILE_OUTPUT"
trap "aptfile_cleanup_preprocessed_source" err exit
trap "aptfile_cleanup_preprocessed_source; exit 1" int
}
aptfile_cleanup_preprocessed_source() {
rm -f "$APTFILE_TMPNAME"
rm -f "$APTFILE_OUTPUT"
}
aptfile_preprocess_source "$APTFILE_INPUT"
export -f update
export -f package
export -f package_from_url
export -f packagelist
export -f ppa
export -f repository
export -f repository_file
export -f debconf_selection
export -f log_fail
export -f log_info
chmod +x "$APTFILE_OUTPUT"
exec "$APTFILE_OUTPUT"