feature: add gpg (GnuPG)
authorAJ ONeal <aj@therootcompany.com>
Wed, 10 Nov 2021 11:22:31 +0000 (11:22 +0000)
committerAJ ONeal <aj@therootcompany.com>
Sun, 14 Nov 2021 08:18:32 +0000 (08:18 +0000)
_webi/template.sh
gnupg/install.sh [new file with mode: 0644]
gpg/README.md [new file with mode: 0644]
gpg/install.ps1 [new file with mode: 0644]
gpg/install.sh [new file with mode: 0644]
gpg/releases.js [new file with mode: 0644]

index adbc3e98e47ad6c1797409e666bc96db7b50b7d0..0ca652bc9ed6f2ac1042317d44d103dbcb608c87 100644 (file)
@@ -35,6 +35,7 @@ function __bootstrap_webi() {
     #PKG_ARCHES=
     #PKG_FORMATS=
     WEBI_UA="$(uname -a)"
+    WEBI_PKG_DOWNLOAD=""
     export WEBI_HOST
 
     ##
@@ -152,6 +153,9 @@ function __bootstrap_webi() {
             my_dl="$HOME/Downloads/$WEBI_PKG_FILE"
         fi
 
+        WEBI_PKG_DOWNLOAD="${my_dl}"
+        export WEBI_PKG_DOWNLOAD
+
         if [ -e "$my_dl" ]; then
             echo "Found $my_dl"
             return 0
diff --git a/gnupg/install.sh b/gnupg/install.sh
new file mode 100644 (file)
index 0000000..8f4595b
--- /dev/null
@@ -0,0 +1,10 @@
+# title: GnuPG (gpg alias)
+# homepage: https://webinstall.dev/gpg
+# tagline: Alias for https://webinstall.dev/gpg
+# alias: gpg
+# description: |
+#   See https://webinstall.dev/gpg
+
+echo "'gnupg@${WEBI_TAG:-stable}' is an alias for 'gpg@${WEBI_VERSION:-}'"
+WEBI_HOST=${WEBI_HOST:-"https://webinstall.dev"}
+curl -fsSL "$WEBI_HOST/gpg@${WEBI_VERSION:-}" | bash
diff --git a/gpg/README.md b/gpg/README.md
new file mode 100644 (file)
index 0000000..89f5a02
--- /dev/null
@@ -0,0 +1,152 @@
+---
+title: Gnu Privacy Guard
+homepage: https://gnupg.org/
+tagline: |
+  GnuPG: a complete implementation of OpenPGP (RFC4880), also known as **P**retty **G**ood **P**rivacy.
+---
+
+### Before you start
+
+If `~/.gitconfig` exists and has both `name` and `email` fields, then a new gpg
+key will be created after the install. Otherwise, you'll have to create one
+yourself.
+
+## Cheat Sheet
+
+> Among other things, gpg is particularly useful for signing and verifying git
+> commits (and emails too).
+
+Here we'll cover:
+
+- Important GPG Files & Directories
+- Creating New Keys
+- Listing Keys
+- Signing Git Commits
+- Exporting GPG Keys for GitHub
+- Publishing GPG Keys to "the Blockchain"
+- Running GPG Agent with launchd
+
+### Files
+
+These are the files / directories that are created and/or modified with this
+install:
+
+```txt
+~/.config/envman/PATH.env
+~/.local/opt/gnupg/bin/gpg
+~/.local/opt/gnupg/bin/gpg-agent
+~/.local/opt/gnupg/bin/pinentry-mac.app/Contents/MacOS/pinentry-mac
+~/.gnupg/gpg-agent.conf
+~/Library/LaunchAgent/gpg-agent.plist
+```
+
+### How to create a new GPG key
+
+See the [Cheat Sheet](./gpg-pubkey) at [gpg-pubkey](./gpg-pubkey).
+
+### How to List GPG Key(s)
+
+```bash
+gpg --list-secret-keys --keyid-format LONG
+```
+
+### How to configure git to sign commits
+
+See the [Cheat Sheet](./git-gpg-init) at [gpg-pubkey](./git-gpg-init).
+
+### How to Export GPG Key for GitHub
+
+See the [Cheat Sheet](./gpg-pubkey) at [gpg-pubkey](./gpg-pubkey).
+
+### How to Publish GPG Keys
+
+GPG is the OG "blockchain", as it were.
+
+If you'd like to publish your (public) key(s) to the public Key Servers for time
+and all eternity, you can:
+
+```bash
+gpg --send-keys "${MY_KEY_ID}"
+```
+
+(no IPFS needed ðŸ˜‰)
+
+### How to start gpg-agent with launchd
+
+(**Note**: this is **done for you** on install, but provided here for reference)
+
+It's a trick question: You can't.
+
+You need to use `gpg-connect-agent` instead.
+
+`~/Library/LaunchAgents/gpg-agent.plist`:
+
+```xml
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>Label</key>
+       <string>gpg-agent</string>
+       <key>ProgramArguments</key>
+       <array>
+               <string>MY_HOME/.local/opt/gpg/bin/gpg-connect-agent</string>
+               <string>--agent-program</string>
+               <string>MY_HOME/.local/opt/gnupg/bin/gpg-agent</string>
+               <string>--homedir</string>
+               <string>MY_HOME/.gnupg/</string>
+               <string>/bye</string>
+       </array>
+
+       <key>RunAtLoad</key>
+       <true/>
+
+       <key>WorkingDirectory</key>
+       <string>MY_HOME</string>
+
+       <key>StandardErrorPath</key>
+       <string>MY_HOME/.local/share/gpg-agent/var/log/gpg-agent.log</string>
+       <key>StandardOutPath</key>
+       <string>MY_HOME/.local/share/gpg-agent/var/log/gpg-agent.log</string>
+</dict>
+</plist>
+```
+
+And then start it with launchctl:
+
+```bash
+launchctl load -w ~/Library/LaunchAgents/gpg-agent.plist
+```
+
+### Troubleshooting 'gpg failed to sign the data'
+
+`gpg` is generally expected to be used with a Desktop client. On Linux servers
+you may get this error:
+
+```txt
+error: gpg failed to sign the data
+fatal: failed to write commit object
+```
+
+Try to load the `gpg-agent`, set `GPG_TTY`, and then run a clearsign test.
+
+```bash
+gpg-connect-agent /bye
+export GPG_TTY=$(tty)
+echo "test" | gpg --clearsign
+```
+
+If that works, update your `~/.bashrc`, `~/.zshrc`, and/or
+`~/.config/fish/config.fish` to include the following:
+
+```bash
+gpg-connect-agent /bye
+export GPG_TTY=$(tty)
+```
+
+If this is failing on Mac or Windows, then `gpg-agent` is not starting as
+expected on login (for Mac the above may work), and/or the `pinentry` command is
+not in the PATH.
+
+If you just installed `gpg`, try closing and reopening your Terminal, or
+possibly rebooting.
diff --git a/gpg/install.ps1 b/gpg/install.ps1
new file mode 100644 (file)
index 0000000..d4dbf97
--- /dev/null
@@ -0,0 +1,6 @@
+#!/usr/bin/env pwsh
+
+echo "We don't yet have a way to automate the GnuPG Tools installer at the user level. In the meantime, try this:"
+echo ""
+echo "    https://gnupg.org/download/#binary"
+echo ""
diff --git a/gpg/install.sh b/gpg/install.sh
new file mode 100644 (file)
index 0000000..0a19fa4
--- /dev/null
@@ -0,0 +1,133 @@
+#!/bin/bash
+
+set -e
+set -u
+
+function _install_gpg() {
+    if ! (uname -a | grep -i "darwin" > /dev/null); then
+        echo "No gpg installer for Linux yet. Try this instead:"
+        echo "    sudo apt install -y gpg gnupg"
+        exit 1
+    fi
+
+    # Download the latest LTS
+    #curl -fsSL -o ~/Downloads/GnuPG-2.2.32.dmg 'https://sourceforge.net/projects/gpgosx/files/GnuPG-2.2.32.dmg/download'
+    webi_download
+    chmod a-w "${WEBI_PKG_DOWNLOAD}"
+
+    # Mount the DMG in /Volumes
+    hdiutil detach -quiet /Volumes/GnuPG* 2> /dev/null || true
+    hdiutil attach -quiet -readonly "${WEBI_PKG_DOWNLOAD}"
+
+    # Extract (completely) to ~/Downloads/GnuGP-VERSION.d
+    # (and detach the DMG)
+    rm -rf ~/Downloads/GnuPG-"${WEBI_VERSION}".d
+    pkgutil --expand-full /Volumes/GnuPG*/*.pkg ~/Downloads/GnuPG-"${WEBI_VERSION}".d
+    hdiutil detach -quiet /Volumes/GnuPG*
+
+    # Move to ~/.local/opt/gnugp (where it belongs!)
+    if [[ ! -e ~/.local/opt/gnupg-"${WEBI_VERSION}" ]]; then
+        mv ~/Downloads/GnuPG-"${WEBI_VERSION}".d/GnuPG.pkg/Payload/ ~/.local/opt/gnupg-"${WEBI_VERSION}"
+    fi
+
+    # Update symlink to latest
+    rm -rf ~/.local/opt/gnupg
+    ln -s gnupg-"${WEBI_VERSION}" ~/.local/opt/gnupg
+
+    pathman add ~/.local/opt/gnupg/bin
+    export PATH="$HOME/.local/opt/gnupg/bin/:$PATH"
+
+    # Prep for first use
+    mkdir -p ~/.gnupg/
+    chmod 0700 ~/.gnupg/
+    if [[ ! -e ~/.gnupg/gpg-agent.conf ]] || ! grep 'pinentry-program' ~/.gnupg/gpg-agent.conf; then
+        echo "pinentry-program $HOME/.local/opt/gnupg/bin/pinentry-mac.app/Contents/MacOS/pinentry-mac" >> ~/.gnupg/gpg-agent.conf
+    fi
+
+    # Start with launchd
+    mkdir -p ~/Library/LaunchAgents/
+    launchctl unload -w ~/Library/LaunchAgents/gpg-agent.plist 2> /dev/null || true
+    # TODO download and use sed to replace
+    echo '<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>Label</key>
+       <string>gpg-agent</string>
+       <key>ProgramArguments</key>
+       <array>
+               <string>'"${HOME}"'/.local/opt/gpg/bin/gpg-connect-agent</string>
+               <string>--agent-program</string>
+               <string>'"${HOME}"'/.local/opt/gnupg/bin/gpg-agent</string>
+               <string>--homedir</string>
+               <string>'"${HOME}"'/.gnupg/</string>
+               <string>/bye</string>
+       </array>
+
+       <key>RunAtLoad</key>
+       <true/>
+
+       <key>WorkingDirectory</key>
+       <string>'"${HOME}"'</string>
+
+       <key>StandardErrorPath</key>
+       <string>'"${HOME}"'/.local/share/gpg-agent/var/log/gpg-agent.log</string>
+       <key>StandardOutPath</key>
+       <string>'"${HOME}"'/.local/share/gpg-agent/var/log/gpg-agent.log</string>
+</dict>
+</plist>' > ~/Library/LaunchAgents/gpg-agent.plist
+    launchctl load -w ~/Library/LaunchAgents/gpg-agent.plist
+    sleep 3
+
+    # (maybe) Create first key
+    if ! gpg --list-secret-keys | grep -q sec; then
+        _create_gpg_key
+    fi
+}
+
+function _create_gpg_key() {
+    if [[ ! -e ~/.gitconfig ]]; then
+        return 0
+    fi
+
+    MY_NAME="$(
+        grep 'name\s*=' ~/.gitconfig |
+            head -n 1 |
+            cut -d'=' -f2 |
+            sed -e 's/^[\t ]*//'
+    )"
+    if [[ -z ${MY_NAME} ]]; then
+        return 0
+    fi
+
+    MY_EMAIL="$(
+        grep 'email\s*=.*@' ~/.gitconfig |
+            tr -d '\t ' | head -n 1 |
+            cut -d'=' -f2
+    )"
+    if [[ -z ${MY_EMAIL} ]]; then
+        return 0
+    fi
+
+    MY_HOST="$(hostname)"
+
+    # Without passphrase:
+    #gpg --batch --generate-key --pinentry=loopback --passphrase=''
+
+    # With passphrase via macOS Keychain
+    gpg --batch --yes --generate-key << EOF
+     %echo Generating RSA 3072 key
+     Key-Type: RSA
+     Key-Length: 3072
+     Subkey-Type: RSA
+     Subkey-Length: 3072
+     Name-Real: ${MY_NAME}
+     Name-Comment: ${MY_HOST}
+     Name-Email: ${MY_EMAIL}
+     Expire-Date: 0
+     %commit
+EOF
+
+}
+
+_install_gpg
diff --git a/gpg/releases.js b/gpg/releases.js
new file mode 100644 (file)
index 0000000..7ab8f5f
--- /dev/null
@@ -0,0 +1,91 @@
+'use strict';
+
+let ltsRe = /GnuPG-(2\.2\.[\d\.]+)/;
+
+function createRssMatcher() {
+  return new RegExp(
+    '<link>(https://sourceforge\\.net/projects/gpgosx/files/GnuPG-([\\d\\.]+)\\.dmg/download)</link>',
+    'g'
+  );
+}
+
+function createUrlMatcher() {
+  return new RegExp(
+    'https://sourceforge\\.net/projects/gpgosx/files/(GnuPG-([\\d\\.]+)\\.dmg)/download',
+    ''
+  );
+}
+
+async function getRawReleases(request) {
+  let matcher = createRssMatcher();
+
+  let resp = await request({
+    url: 'https://sourceforge.net/projects/gpgosx/rss?path=/'
+  });
+  let links = [];
+  for (;;) {
+    let m = matcher.exec(resp.body);
+    if (!m) {
+      break;
+    }
+    links.push(m[1]);
+  }
+  return links;
+}
+
+function transformReleases(links) {
+  //console.log(JSON.stringify(links, null, 2));
+  //console.log(links.length);
+
+  let matcher = createUrlMatcher();
+
+  let releases = links
+    .map(function (link) {
+      // strip 'go' prefix, standardize version
+      let isLts = ltsRe.test(link);
+      let parts = link.match(matcher);
+      if (!parts || !parts[2]) {
+        return null;
+      }
+      let segs = parts[2].split('.');
+      let version = segs.slice(0, 3).join('.');
+      if (segs.length > 3) {
+        version += '+' + segs.slice(3);
+      }
+
+      return {
+        name: parts[1],
+        version: version,
+        // all go versions >= 1.0.0 are effectively LTS
+        lts: isLts,
+        channel: 'stable',
+        // TODO <pubDate>Sat, 19 Nov 2016 16:17:33 UT</pubDate>
+        date: '1970-01-01', // the world may never know
+        os: 'macos',
+        arch: 'amd64',
+        ext: 'dmg',
+        download: link
+      };
+    })
+    .filter(Boolean);
+
+  return {
+    releases: releases
+  };
+}
+
+async function getAllReleases(request) {
+  let releases = await getRawReleases(request);
+  let all = transformReleases(releases);
+  return all;
+}
+
+module.exports = getAllReleases;
+
+if (module === require.main) {
+  getAllReleases(require('@root/request')).then(function (all) {
+    all = require('../_webi/normalize.js')(all);
+    all.releases = all.releases.slice(0, 10000);
+    console.info(JSON.stringify(all, null, 2));
+  });
+}