orgstrap
tricks don’t
do us very much good.
The following section tangles a nearly unreadable file that can be used to bootstrap Emacs onto on a variety of systems. The blocks below break it up into readable chunks.
I’m not sure that anyone should use it, but it seems easier than other approaches I have taken for trying to get Emacs installed on a non-technical or semi-technical user’s system. The variety of different ways that one can obtain, install, configure, and customize Emacs makes it particularly bewildering.
doom-emacs
bin/doom
provided the initial inspiration for this
solution[fn::Not that anyone should ever want to be inspired to
create such a monstrosity. But hey, if software packaging and
distribution were a solve problem we wouldn’t be doing this right
now and there would be no such thing as cybercrime and we would
all live in a wild happy techno-utopian future …]. The inspiration
for the problem was entirely of my own making and comes from a painful
experience trying write instructions that were followable[fn::
https://github.com/SciCrunch/sparc-curation/blob/master/docs/setup.org#setup]
for how to run a block in an Org file from scratch, thinking that if
they could just manage to do that then hard part of the setup would
be handled for them by the code in the Org mode file. Live and learn.
So here we are, not quite at curl pipe bash, but hopefully at a form that is slightly more auditable[fn::Which means that this section may need to be broken out into its own file and published separately.].
At this point the basics of the script should be working on darwin, various linux distros, various bsds, and windows. At the moment only windows and a subset of linux distros can bootstrap from scratch. There is also a question of whether forcing a package manager on an unsuspecting user is a good idea. I tend to say yes, but maybe we need to make sure that they are a little less unsuspecting? Though there is nothing like a cold baptism into the world of proper systems administration.
Below is what appears to be nothing more than an Emacs lisp block with 3 other blocks nowebed into the … comments? What is going on here?!
The quoted colons and comments are no-ops in both powershell and bash/zsh so we can include the required shell functionality safely hidden from elisp behind the comments.
The three nowebbed blocks are implemented in the following sections.
":"; # -*-emacs-lisp-*-
":"; <<posix-powershell-switch>>
":"; #px <<posix-bootstrap>>
":"; #ps <<powershell-bootstrap>>
(message "We have Emacs.")
Bash
Bash in posix mode.
If you try to run this block via org-babel on a Debian derived
distro this will fail, see the :shebang
header for details.
Zsh
Powershell
Right now the switch only works with bash, zsh, and powershell because
https://unix.stackexchange.com/a/71137 is more than I want to try to
deal with here. It will also run under sh
if it points to bash
running in --posix
mode.
It is unfortunate that there is no way to get this to work with dash,
otherwise we could set sh
for the shebang, but we can’t due to the
incompatible function syntax.
The approach comes from https://stackoverflow.com/a/39422067. The block below
is syntactically valid bash, zsh, and powershell. In a posix shell the quoted
commands and "exit"
run, whereas in powershell they are skipped. We then engage
in some unspeakable hackery and parse the input file and grep for #px
or #ps
to select the code for the specific platform (see ref:bootstrap-from-shell).
We then eval it[fn::If you are still nodding along in agreement, know that
I am running in terror from this code block even as I write this footnote.].
function posix () {
eval "$(grep '^":";\ #px' $0 | sed -e 's/^":";\ #px //')"
return "$?"
}
function quirk () {
test "$ZSH_VERSION" && set +nofunctionargzero
}
function unquirk () {
test "$ZSH_VERSION" && set nofunctionargzero
}
"quirk"
"posix"
"unquirk"
"exit" # NOTE can't pass error code out easily here
((Get-Content $MyInvocation.MyCommand.Source) -match '^":";\ #ps ' -replace '^":";\ #ps ') -join "`n" | Invoke-Expression
exit
Many variants of the test for quirk have been tried and failed. The
variant below works but was not selected because test
is more
portable (see https://stackoverflow.com/a/6535252).
function quirk () {
(( $ZSH_VERSION )) && set +nofunctionargzero
}
Furthermore, test
is compatible with powershell syntax so it is better to use that
for bootstrapping. On all my windows systems test
is available, but I suspect
that it is coming from something I have installed and cannot be relied upon.
Quirks are required for zsh due to the fact that it will set $0
to the name
of the function which breaks grepping $0
since we encounter the name of the
function not the name of the source file.
Test blocks to ensure shell portability.
Emacs missing, preparing to bootstrap.
Package manager commands.
emerge apt yum dnf pacaman nix-env guix brew
We start with builtin package managers rather than parasitic package managers because anyone using one of those should know enough to either already have emacs installed or to figure out how to get it installed without this script.
We don’t use hyphen-minus -
in function names so that we can support bash running in posix mode.
You would think that it would be easier to securely retrieve a file from the internet and check that its checksum matches, but just handling the cases for existing files and checksum failures is enough to increase the size and we haven’t even dealt with supporting different cyphers yet! Some deduplication could be achieved by creating another function or two.
echo "Bootstrapping in posix mode."
scurl () {
# safe(r) curl, yet still scurrilous thus scurl
# example: scurl my-audited-checksum https://example.org/file.ext /tmp/file.ext
local CYPHER="${1}"
local CHECKSUM="${2}"
local URL="${3}"
local path="${4}"
local dname="$(dirname "${path}")"
local fname="$(basename "${path}")"
if [ $CYPHER != sha256 ]; then
# TODO cypher command ...
echo "Unsupported cypher ${CYPHER}"
return 1
fi
if [ -f "${path}" ]; then
echo "$(sha256sum "${path}" 2>/dev/null || shasum -a 256 "${path}")" | \
awk '$1!="'"${CHECKSUM}"'" { exit 1 }'
local CODE=$?
if [ $CODE -ne 0 ]; then
echo failed with $CODE
echo "${path}" existing checksum does not match new checksum.
local efail_path="$(mktemp -p "${dname}" "${fname}"._existing_failure_.XXXXXX)"
mv "${path}" "${efail_path}"
else
return 0
fi
fi
local temp_path="$(mktemp -p "${dname}" "${fname}"._fetching_.XXXXXX)"
#cmd="$(command -v sha256sum 2>/dev/null || (shasum -a 256))"
# too many ways a streaming check can go wrong that we can't handle correclty
# set -o pipefail is not portable
#curl --location "${URL}" | tee "${temp_path}" | ${cmd} || return $?
curl --location "${URL}" --output "${temp_path}" || return $?
echo "$(sha256sum "${temp_path}" 2>/dev/null || shasum -a 256 "${temp_path}")" | \
awk '$1!="'"${CHECKSUM}"'" { exit 1 }'
local CODE=$?
if [ $CODE -ne 0 ]; then
echo failed with $CODE
echo "${temp_path}" checksum did not pass! something evil is going on!
local fail_path="$(mktemp -p "${dname}" "${fname}"._checksum_failure_.XXXXXX)"
mv "${temp_path}" "${fail_path}"
else
mv "${temp_path}" "${path}"
fi
}
function install_homebrew {
echo "Not implemented yet!"
return 1
local path_install_sh
path_install_sh="$(mktemp -d)/install.sh" # FIXME
scurl sha256 "<<&brew-install.sh-checksum()>>" "<<&brew-install.sh-url()>>" "${path_isntall_sh}"
/usr/bin/env bash "${path_install_sh}"
# TODO cleanup after ourselves.
}
function package_manager {
local full;
cmds=(<<&var-cmds()>>)
for cmd in "${cmds[@]}"; do
full=$(command -v ${cmd} 2>&1) && break;
done
echo "${cmd}"
}
function posix_bootstrap {
local nopm
local cmd
cmd=$(package_manager)
case $cmd in
emerge) $cmd app-editors/emacs ;;
yum) $cmd -y install emacs-nox ;;
dnf) $cmd -y install emacs-nox ;;
pacman) $cmd --noconfirm -S emacs-nox ;;
apt) $cmd -y install emacs-nox ;;
nix-env) $cmd -i emacs-nox ;;
guix) $cmd install emacs-nox ;;
brew) $cmd cask install emacs ;;
*) nopm=1; echo No package manager found! Checked <<&var-cmds()>>. ;;
esac
local CODE=$?
# none -> os detection -> get the right one -> run this again
if [ -n "${nopm}" ]; then
if [ ${OSTYPE%-*} = darwin ]; then
install_homebrew && posix_bootstrap
CODE=$?
else
echo "Don't know how to install a package manager for ${OSTYPE}."
CODE=1
fi
fi
# should probably run emacs in here ??
return $CODE
}
function missing_emacs {
echo "<<&message-emacs-missing()>>"
local BCMD="$(typeset -f package_manager); $(typeset -f posix_bootstrap); posix_bootstrap"
if command -v sudo; then
sudo "$0" -c "${BCMD}" || exit $?
else
echo For su on ${HOSTNAME} 1>& 2;
su -c "${BCMD}" || exit $?
fi
}
# FIXME sort out the argument passing
( echo "${EMACS}" | grep -q "term" ) && EMACS=emacs || EMACS=${EMACS:-emacs}
command -v $EMACS >/dev/null || missing_emacs &&
$EMACS --no-site-file --script "$0" -- "$@"
CODE=$?
exit $CODE
Latest audited package manager install urls and checksums.
https://raw.githubusercontent.com/Homebrew/install/fea1e80dd6c80ff0ac64e0e78afa387179f08660/install.sh
45c12bcd7765986674142230fc0860f5274903adbe37d582e5537771a1bae0b8
Homebrew is my suggested package manager for macos. The threat model
that is latent in the version of install.sh at fea1e80d
that I have
audited does not make any attempt to counter https MITM attacks, nor
attacks against the main brew git repository. For example, it is not
possible to install known safe versions via install.sh
because the
script always installs from master and there is no attempt to verify
the integrity of what is pulled from github over https.
If you are not comfortable with a threat model that is vulnerable to https MITM attacks then don’t use this. For most users this is not an issue.
Other than that, fea1e80d
looks ok.
FIXME I do NOT like the fact that choco is effectively pulling a curl | bash
here.
This needs a signature.
Write-Host "Bootstrapping in windows mode."
$EMACS = if ($Env:EMACS -eq $null) { "emacs" } else { $Env:EMACS }
function bootstrap-windows {
if (-not (Get-Command $EMACS -errorAction SilentlyContinue)) {
Write-Host "<<&message-emacs-missing()>>"
if (-not (Get-Command choco -errorAction SilentlyContinue)) {
Write-Host "Chocolatey missing, preparing to bootstrap."
Write-Host "Install chocolatey? [y/N]"
if ('y', 'Y' -contains $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown").Character) {
Set-ExecutionPolicy AllSigned -Scope Process -Force;
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072;
Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
} else { Write-Host "Not installing chocolatey. If you want to continue you can install emacs manually.";
throw "failed" } }
choco install $EMACS } }
# FIXME may also need to use $MyInvocation.MyCommand.Path .Name
try {
bootstrap-windows
emacs --script $MyInvocation.MyCommand.Source -- $args
} finally {
exit
}