Skip to content

Instantly share code, notes, and snippets.

@dch dch/app.sh
Last active Mar 14, 2019

Embed
What would you like to do?
#!/bin/sh -e
# obvious
export APP=$(basename -s .sh $0)
# derived paths are OS dependent
export ERTS=$(find /usr/local/lib/erlang* -type d -depth 1 -name erts-\* | tail -1)
export VERSION=$(cut -swf 2 /usr/local/lib/${APP}/releases/start_erl.data)
# config files
CONFIGS=/usr/local/etc/${APP}
export SYSCONFIG=${CONFIGS}/sys.config
export VMARGS=${CONFIGS}/vm.args
# if this dir is not writable then the runtime can't start
export HOME=/var/run/${APP}
# custom parameters
# BEAM essentials
export LD_LIBRARY_PATH=${ERTS}/lib:
export BINDIR=${ERTS}/bin
export PATH=${BINDIR}:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
export ROOTDIR=/usr/local/lib/${APP}
export PROGNAME=${ROOTDIR}/releases/${VERSION}/${APP}.sh
export ERL_LIBS=${ROOTDIR}/lib:${ERTS}/../lib
export MODE="-mode embedded"
# elixir
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
# useful BEAM bits
# export ERL_AFLAGS="-kernel shell_history enabled"
# no turds on BEAM exit
export ERL_CRASH_DUMP=/dev/null
export ERL_CRASH_DUMP_BYTES=0
export ERL_CRASH_DUMP_SECONDS=0
# debug beam startup
# export VERBOSE="-init_debug "
# ensure epmd is managed by OS daemons and not tied to this app
# export EPMD="-start_epmd false"
# keep user sticky fingers out of the console
# see http://erlang.org/doc/man/run_erl.html
# export RUN_ERL_DISABLE_FLOWCNTRL=1
# export DOTERL="-boot no_dot_erlang"
cd /usr/local/lib/${APP}
${BINDIR}/erlexec \
${VERBOSE} \
${DOTERL} \
${EPMD} \
${MODE} \
-boot ${ROOTDIR}/releases/${VERSION}/${APP} \
-boot_var ERTS_LIB_DIR ${ERTS}/../lib \
-env ERL_LIBS ./rel/${APP}/lib \
-pa ${ROOTDIR}/lib/${APP}-${VERSION}/consolidated \
-pz ${ROOTDIR}/lib/${APP}-${VERSION}/ebin \
-args_file ${VMARGS} \
-config ${SYSCONFIG} \
-user Elixir.IEx.CLI \
-extra \
--no-halt \
+iex -- console
#!/bin/sh -ex
# elixir puts its _build dir in the root of this repo, alongside build.sh
cd $(dirname $0)
ROOT=$(pwd -P)
APP=$(basename $ROOT)
BUILD=${ROOT}/_build
cd -
# borrow rebar from packages
test -x ${HOME}/.mix/rebar || mix local.rebar --force rebar /usr/local/bin/rebar
test -x ${HOME}/.mix/rebar3 || mix local.rebar --force rebar3 /usr/local/bin/rebar3
# ensure we pick up OTP21 by default
export PATH=/usr/local/lib/erlang21/bin:$PATH
# UTF8 while we build
export LANG=en_US.UTF-8
export LC_ALL=${LANG}
export MIX_ENV=prod
# clean up any existing release tarballs
rm -rf $(find ${ROOT}/rel ${BUILD} -name *z) 2>/dev/null
# clean up existing dependencies and build
rm -rf ${ROOT}/deps
# build stuff
cp config/prod.secret.exs.example config/prod.secret.exs
mix deps.get --only prod
mix release --env=prod
RELEASE=$(find ${ROOT} -name ${APP}*tar.gz) 2>/dev/null
test -e "${RELEASE}" || exit 1
# use a random temporary dir for everything else
BASE=$(mktemp -d /tmp/${APP}.XXXXXX)
STAGING=${BASE}/staging
DEST=${STAGING}/usr/local/lib/${APP}
PKG=${BASE}/package
MANIFEST=${PKG}/manifest.ucl
mkdir -p -m0750 ${PKG} ${DEST} \
${STAGING}/usr/local/etc/rc.d \
${STAGING}/usr/local/bin
# exclude non-root users during tar etc
umask 027
# get template manifest and other static files
cp -vp ${ROOT}/rc.d ${STAGING}/usr/local/etc/rc.d/${APP}
cp -vp ${ROOT}/app.sh ${STAGING}/usr/local/bin/${APP}
cp -vp ${ROOT}/manifest.ucl ${MANIFEST}
# replace APP with actual app name in rc.d script
sed -E -i '' -e "s/APP/${APP}/g" ${STAGING}/usr/local/etc/rc.d/${APP}
# unpack the mix release itself excluding files with tokens
# these files should be distributed out of band via ops tools
tar xzf ${RELEASE} \
-C ${DEST} \
--exclude sys.config \
--exclude vm.args
# prepare FreeBSD package manifest
## grab the version number from mix
DATE=$(date -u +%Y%m%d-%H%M)
# get a git tag or nearest sha to use as a suffix to app version
GITREF=$(git describe --dirty --abbrev=7 --tags --always --first-parent 2>/dev/null || true)
## inject all that into the manifest
uclcmd set --file ${MANIFEST} --type string version ${DATE}-${GITREF}
# include each file, its hash, and any permissions:
# expected result:
# /usr/lib/app/bin/app: {sum: 1$abc123, uname: root, gname: www, perm: 0440 }
SHA_LIST=$(find ${STAGING} -type f -exec sha256 -r {} + \
| awk '{print " " $2 ": {uname: root, gname: www, sum: 1$" $1"}"}')
# include softlinks:
# expected result looks like:
# /usr/local/lib/symlink: -
LINKS_LIST=$(find ${STAGING} -type l \
| awk '{print " " $1 ": -"}')
# include app-specific directories and permissions:
# make sure we exclude things like /usr /usr/local/ /etc/ that are
# already in place as we don't want to change their permissions
# expected result looks like:
# /usr/local/lib/app: {uname: root, gname: www, perm: 0550}`
DIR_LIST=$(find ${STAGING} -type d -mindepth 3 -path \*/${APP}/\* \
| awk '{print " " $1 ": {uname: root, gname: www, perm: 0750}"}')
# make runtime directories
mkdir -m0770 -p \
${STAGING}/usr/local/etc/${APP} \
${STAGING}/var/db/${APP} \
${STAGING}/var/log/${APP} \
${STAGING}/var/run/${APP}
# strip off _build/state prefix and append this UCL snippet to manifest
cat <<UCL | sed -E -e s:${STAGING}:: >> ${MANIFEST}
files: {
$SHA_LIST
$LINKS_LIST
}
directories: {
$DIR_LIST
/usr/local/etc/${APP}: {uname: root, gname: www, perm: 0770}
/var/log/${APP}: {uname: root, gname: www, perm: 0770}
/var/run/${APP}: {uname: root, gname: www, perm: 0770}
/var/db/${APP}: {uname: root, gname: www, perm: 0770}
}
UCL
## bubblewrap the package and manifest
if test "$(uname -s)" == "FreeBSD"; then
pkg create --verbose \
--root-dir ${STAGING} \
--manifest ${MANIFEST} \
--out-dir ${BUILD}
cp ${MANIFEST} ${BUILD}/
cat << EOF
final manifest: $(find ${BUILD} -name manifest.ucl)
package complete: $(find ${BUILD} -name *.txz)
to install:
sudo -s
service ${APP} stop
pkg install $(find ${BUILD} -name ${APP}\*.txz)
service ${APP} start
EOF
fi
name: app
origin: skunkwerks/app
comment: "The first draft of anything is shit."
arch: freebsd:12:x86:64
www: https://github.com/skunkwerks/app
maintainer: support@skunkwerks.at
prefix: /usr/local
licenselogic: single
licenses: [MIT]
categories: [skunkwerks]
deps: {erlang-runtime21: {origin: lang/erlang-runtime21, version: "21"}}
flatsize: 0
desc: <<EOD
There is nothing to writing. All you do is sit down at a typewriter and bleed.
EOD
message: <<EOM
The best people possess a feeling for beauty, the courage to take risks, the
discipline to tell the truth, the capacity for sacrifice. Ironically, their
virtues make them vulnerable; they are often wounded, sometimes destroyed.
Ensure that you have the following config files in place:
- /etc/rc.conf.d/app
- /usr/local/etc/app/vm.args
- /usr/local/etc/app/sys.config
EOM
#!/bin/sh
#
# PROVIDE: APP
# REQUIRE: networking epmd
# AFTER: epmd
# KEYWORD:
. /etc/rc.subr
name="APP"
rcvar="${name}_enable"
install_dir="/usr/local/lib/${name}"
version=$(cut -wf 2 ${install_dir}/releases/start_erl.data)
extra_commands="status"
start_cmd="${name}_start"
stop_cmd="${name}_stop"
status_cmd="${name}_status"
load_rc_config $name
: ${APP_enable:="no"}
: ${APP_verbose:=""}
: ${APP_user:="www"}
: ${APP_command=/usr/local/bin/${name}}
APP_run()
{
umask 027
/usr/bin/env \
ERL_ZFLAGS="-detached" \
su -m "${APP_user}" -c "${APP_command}"
}
# On each run, we ensure we are starting from a clean slate.
# At shutdown we kill any stray processes just in case.
# Logs are stored using syslog but there are some minimal
# startup and heart logs from the runtime that are worth
# keeping in case of debugging BEAM crashes.
APP_start()
{
rm -rf /var/run/APP/.erlang.cookie
APP_stop
APP_run
}
APP_stop()
{
# kill only the process listed in the pidfile and only if the user matches
pkill -TERM -U ${APP_user} -f beam.smp
sleep 3
pkill -KILL -U ${APP_user} -f beam.smp
}
APP_status()
{
pid_check=$(pgrep -U ${APP_user} -f beam.smp)
test "$pid_check" && echo "${name} is running."
}
load_rc_config $name
run_rc_command "$1"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.