Skip to content

Latest commit

 

History

History
341 lines (316 loc) · 13.9 KB

get-emacs.org

File metadata and controls

341 lines (316 loc) · 13.9 KB

Bootstrapping to Emacs, bootstrapping to Org

If you are dealing with a system that does not have a working version of Emacs then all these fancy 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.

Emacs lisp

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.")

Tests

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

Posix powershell switch

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.

Tests

Test blocks to ensure shell portability.

Common variables

Emacs missing, preparing to bootstrap.

Posix 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

Package managers

Latest audited package manager install urls and checksums.

Homebrew

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.

Powershell bootstrap

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
}