_1password-gui: Add passthru.updateScript (#416072)

Changed files
+288 -109
pkgs
+2
pkgs/by-name/_1/_1password-gui/darwin.nix
···
# 1Password is notarized.
dontFixup = true;
+
+
passthru.updateScript = ./update.sh;
}
+2
pkgs/by-name/_1/_1password-gui/linux.nix
···
# https://1password.community/discussion/comment/624011/#Comment_624011
#--add-flags "\''${NIXOS_OZONE_WL:+\''${WAYLAND_DISPLAY:+--ozone-platform-hint=auto --enable-features=WaylandWindowDecorations --enable-wayland-ime=true}}"
'';
+
+
passthru.updateScript = ./update.sh;
}
+15 -10
pkgs/by-name/_1/_1password-gui/package.nix
···
let
pname = "1password";
-
versions = builtins.fromJSON (builtins.readFile ./versions.json);
-
hostOs = if stdenv.hostPlatform.isLinux then "linux" else "darwin";
-
version = versions."${channel}-${hostOs}" or (throw "unknown channel-os ${channel}-${hostOs}");
+
hostOs = stdenv.hostPlatform.parsed.kernel.name;
+
hostArch = stdenv.hostPlatform.parsed.cpu.name;
+
sources = builtins.fromJSON (builtins.readFile ./sources.json);
-
sources = builtins.fromJSON (builtins.readFile ./sources.json);
+
sourcesChan = sources.${channel} or (throw "unsupported channel ${channel}");
+
sourcesChanOs = sourcesChan.${hostOs} or (throw "unsupported OS ${hostOs}");
+
sourcesChanOsArch =
+
sourcesChanOs.sources.${hostArch} or (throw "unsupported architecture ${hostArch}");
+
inherit (sourcesChanOs) version;
src = fetchurl {
-
inherit
-
(sources.${channel}.${stdenv.system} or (throw "unsupported system ${stdenv.hostPlatform.system}"))
-
url
-
hash
-
;
+
inherit (sourcesChanOsArch) url hash;
};
meta = {
···
sebtm
bdd
];
-
platforms = builtins.attrNames sources.${channel};
+
platforms = [
+
"x86_64-linux"
+
"x86_64-darwin"
+
"aarch64-linux"
+
"aarch64-darwin"
+
];
mainProgram = "1password";
};
+48 -28
pkgs/by-name/_1/_1password-gui/sources.json
···
{
"stable": {
-
"x86_64-linux": {
-
"url": "https://downloads.1password.com/linux/tar/stable/x86_64/1password-8.10.78.x64.tar.gz",
-
"hash": "sha256-COmXSjbCetPsbm40OrWGVtULPheEgnHEO0ZcIgWaG1w="
-
},
-
"aarch64-linux": {
-
"url": "https://downloads.1password.com/linux/tar/stable/aarch64/1password-8.10.78.arm64.tar.gz",
-
"hash": "sha256-diy7VhKRluSnVSR35Ogamf9RDHdqxSJifLOOYmMrJHE="
-
},
-
"x86_64-darwin": {
-
"url": "https://downloads.1password.com/mac/1Password-8.10.78-x86_64.zip",
-
"hash": "sha256-8fbjEc/Z0xCdXq/uHp4bQE5Js5hNLbVCRZxnepUdLUs="
+
"linux": {
+
"version": "8.10.78",
+
"sources": {
+
"x86_64": {
+
"url": "https://downloads.1password.com/linux/tar/stable/x86_64/1password-8.10.78.x64.tar.gz",
+
"hash": "sha256-COmXSjbCetPsbm40OrWGVtULPheEgnHEO0ZcIgWaG1w="
+
},
+
"aarch64": {
+
"url": "https://downloads.1password.com/linux/tar/stable/aarch64/1password-8.10.78.arm64.tar.gz",
+
"hash": "sha256-diy7VhKRluSnVSR35Ogamf9RDHdqxSJifLOOYmMrJHE="
+
}
+
}
},
-
"aarch64-darwin": {
-
"url": "https://downloads.1password.com/mac/1Password-8.10.78-aarch64.zip",
-
"hash": "sha256-x03dZ/eVrvFcbese1cBAvyJKwtWe6rOcgytn0OsEFDQ="
+
"darwin": {
+
"version": "8.10.78",
+
"sources": {
+
"x86_64": {
+
"url": "https://downloads.1password.com/mac/1Password-8.10.78-x86_64.zip",
+
"hash": "sha256-8fbjEc/Z0xCdXq/uHp4bQE5Js5hNLbVCRZxnepUdLUs="
+
},
+
"aarch64": {
+
"url": "https://downloads.1password.com/mac/1Password-8.10.78-aarch64.zip",
+
"hash": "sha256-x03dZ/eVrvFcbese1cBAvyJKwtWe6rOcgytn0OsEFDQ="
+
}
+
}
}
},
"beta": {
-
"x86_64-linux": {
-
"url": "https://downloads.1password.com/linux/tar/beta/x86_64/1password-8.10.80-18.BETA.x64.tar.gz",
-
"hash": "sha256-X2Wu/dQQ7fv+tTAU2/70S38wL6WdJuc/DXWoiHZvSP4="
-
},
-
"aarch64-linux": {
-
"url": "https://downloads.1password.com/linux/tar/beta/aarch64/1password-8.10.80-18.BETA.arm64.tar.gz",
-
"hash": "sha256-52aRg6QD/fKOzOHoG88q8VNJIizxnISFnpxek7bJ05w="
-
},
-
"x86_64-darwin": {
-
"url": "https://downloads.1password.com/mac/1Password-8.10.80-18.BETA-x86_64.zip",
-
"hash": "sha256-kUU+nm19DmdY8ZG6d+EJFQXcCy/BOauXh83suQLSvz0="
+
"linux": {
+
"version": "8.10.80-18.BETA",
+
"sources": {
+
"x86_64": {
+
"url": "https://downloads.1password.com/linux/tar/beta/x86_64/1password-8.10.80-18.BETA.x64.tar.gz",
+
"hash": "sha256-X2Wu/dQQ7fv+tTAU2/70S38wL6WdJuc/DXWoiHZvSP4="
+
},
+
"aarch64": {
+
"url": "https://downloads.1password.com/linux/tar/beta/aarch64/1password-8.10.80-18.BETA.arm64.tar.gz",
+
"hash": "sha256-52aRg6QD/fKOzOHoG88q8VNJIizxnISFnpxek7bJ05w="
+
}
+
}
},
-
"aarch64-darwin": {
-
"url": "https://downloads.1password.com/mac/1Password-8.10.80-18.BETA-aarch64.zip",
-
"hash": "sha256-eZG0QaB5NRwRCYcmlfZA/HTceLq7eUzR+AvzDeOrzAY="
+
"darwin": {
+
"version": "8.10.80-18.BETA",
+
"sources": {
+
"x86_64": {
+
"url": "https://downloads.1password.com/mac/1Password-8.10.80-18.BETA-x86_64.zip",
+
"hash": "sha256-kUU+nm19DmdY8ZG6d+EJFQXcCy/BOauXh83suQLSvz0="
+
},
+
"aarch64": {
+
"url": "https://downloads.1password.com/mac/1Password-8.10.80-18.BETA-aarch64.zip",
+
"hash": "sha256-eZG0QaB5NRwRCYcmlfZA/HTceLq7eUzR+AvzDeOrzAY="
+
}
+
}
}
}
}
+122
pkgs/by-name/_1/_1password-gui/update-sources.py
···
+
#!/usr/bin/env nix-shell
+
#!nix-shell -i python3 -p python3 gnupg
+
import json
+
import os
+
import shutil
+
import subprocess
+
import sys
+
import tempfile
+
from collections import OrderedDict
+
+
DOWNLOADS_BASE_URL = "https://downloads.1password.com"
+
OP_PGP_KEYID = "3FEF9748469ADBE15DA7CA80AC2D62742012EA22"
+
+
+
class Sources(OrderedDict):
+
def __init__(self):
+
self._jsonfp = open("sources.json", "r+")
+
self.update(json.load(self._jsonfp))
+
self._jsonfp.seek(0, os.SEEK_SET)
+
+
def persist(self):
+
json.dump(self, self._jsonfp, indent=2)
+
self._jsonfp.write("\n") # keep fmt.check happy
+
+
+
class GPG:
+
def __new__(cls):
+
if not hasattr(cls, "_instance"):
+
cls._instance = super().__new__(cls)
+
return cls._instance
+
+
def __init__(self):
+
if hasattr(self, "gnupghome"):
+
return
+
+
self.gpg = shutil.which("gpg")
+
self.gpgv = shutil.which("gpgv")
+
self.gnupghome = tempfile.mkdtemp(prefix="1password-gui-gnupghome.")
+
self.env = {"GNUPGHOME": self.gnupghome}
+
self._run(
+
self.gpg,
+
"--no-default-keyring",
+
"--keyring",
+
"trustedkeys.kbx",
+
"--keyserver",
+
"keyserver.ubuntu.com",
+
"--receive-keys",
+
OP_PGP_KEYID,
+
)
+
+
def __del__(self):
+
shutil.rmtree(self.gnupghome)
+
+
def _run(self, *args):
+
try:
+
subprocess.run(args, env=self.env, check=True, capture_output=True)
+
except subprocess.CalledProcessError as cpe:
+
print(cpe.stderr, file=sys.stderr)
+
raise SystemExit(f"gpg error: {cpe.cmd}")
+
+
def verify(self, sigfile, datafile):
+
return self._run(self.gpgv, sigfile, datafile)
+
+
+
def nix_store_prefetch(url):
+
nix = shutil.which("nix")
+
cp = subprocess.run(
+
[nix, "store", "prefetch-file", "--json", url], check=True, capture_output=True
+
)
+
out = json.loads(cp.stdout)
+
+
return out["storePath"], out["hash"]
+
+
+
def mk_url(channel, os, version, arch):
+
if os == "linux":
+
arch_alias = {"x86_64": "x64", "aarch64": "arm64"}[arch]
+
path = f"linux/tar/{channel}/{arch}/1password-{version}.{arch_alias}.tar.gz"
+
elif os == "darwin":
+
path = f"mac/1Password-{version}-{arch}.zip"
+
else:
+
raise SystemExit(f"update-sources.py: unsupported OS {os}")
+
+
return f"{DOWNLOADS_BASE_URL}/{path}"
+
+
+
def download(channel, os, version, arch):
+
url = mk_url(channel, os, version, arch)
+
store_path_tarball, hash = nix_store_prefetch(url)
+
+
# Linux release tarballs come with detached PGP signatures.
+
if os == "linux":
+
store_path_sig, _ = nix_store_prefetch(url + ".sig")
+
GPG().verify(store_path_sig, store_path_tarball)
+
+
return url, hash
+
+
+
def main(args):
+
"""Gets called with args in `channel/os/version` format.
+
+
e.g.:
+
update-sources.py stable/linux/8.10.80 beta/linux/8.10.82-12.BETA
+
"""
+
sources = Sources()
+
+
for triplet in args[1:]:
+
channel, os, version = triplet.split("/")
+
release = sources[channel][os]
+
if release["version"] == version:
+
continue
+
+
release["version"] = version
+
for arch in release["sources"]:
+
url, hash = download(channel, os, version, arch)
+
release["sources"][arch].update({"url": url, "hash": hash})
+
+
sources.persist()
+
+
+
if __name__ == "__main__":
+
sys.exit(main(sys.argv))
+99 -65
pkgs/by-name/_1/_1password-gui/update.sh
···
#!/usr/bin/env nix-shell
-
#!nix-shell -i bash -p jq gnupg
+
#!nix-shell -i bash -p jq curl
#shellcheck shell=bash
+
set -euo pipefail
+
+
# For Linux version checks we rely on Repology API to check 1Password managed Arch User Repository.
+
REPOLOGY_PROJECT_URI="https://repology.org/api/v1/project/1password"
-
set -euo pipefail
+
# For Darwin version checks we query the same endpoint 1Password 8 for Mac queries.
+
# This is the base URI. For stable channel an additional path of "N", for beta channel, "Y" is required.
+
APP_UPDATES_URI_BASE="https://app-updates.agilebits.com/check/2/99/aarch64/OPM8/en/0/A1"
-
cd -- "$(dirname "${BASH_SOURCE[0]}")"
+
CURL=(
+
"curl" "--silent" "--show-error" "--fail"
+
"--proto" "=https" # enforce https
+
"--tlsv1.2" # do not accept anything below tls 1.2
+
"-H" "user-agent: nixpkgs#_1password-gui update.sh" # repology requires a descriptive user-agent
+
)
-
mk_url() {
-
local \
-
base_url="https://downloads.1password.com" \
-
os="$1" \
-
channel="$2" \
-
arch="$3" \
-
version="$4"
+
JQ=(
+
"jq"
+
"--raw-output"
+
"--exit-status" # exit non-zero if no output is produced
+
)
-
if [[ ${os} == "linux" ]]; then
-
if [[ ${arch} == "x86_64" ]]; then
-
ext="x64.tar.gz"
-
else
-
ext="arm64.tar.gz"
-
fi
-
url="${base_url}/${os}/tar/${channel}/${arch}/1password-${version}.${ext}"
-
else
-
ext="${arch}.zip"
-
url="${base_url}/mac/1Password-${version}-${ext}"
-
fi
-
echo "${url}"
+
read_local_versions() {
+
local channel="$1"
+
+
while IFS='=' read -r key value; do
+
local_versions["${key}"]="${value}"
+
done < <(jq -r --arg channel "${channel}" '
+
.[$channel] | to_entries[] | .key as $os | .value.version as $version |
+
"\($channel)/\($os)=\($version)"
+
' sources.json)
}
-
cleanup() {
-
if [[ -d ${TMP_GNUPGHOME-} ]]; then
-
rm -r "${TMP_GNUPGHOME}"
-
fi
+
read_remote_versions() {
+
local channel="$1"
+
+
if [[ ${channel} == "stable" ]]; then
+
remote_versions["stable/linux"]=$(
+
"${CURL[@]}" "${REPOLOGY_PROJECT_URI}" \
+
| "${JQ[@]}" '.[] | select(.repo == "aur" and .srcname == "1password" and .status == "newest") | .version'
+
)
+
+
remote_versions["stable/darwin"]=$(
+
"${CURL[@]}" "${APP_UPDATES_URI_BASE}/N" \
+
| "${JQ[@]}" 'select(.available == "1") | .version'
+
)
+
else
+
remote_versions["beta/linux"]=$(
+
# AUR version string uses underscores instead of dashes for betas.
+
# We fix that with a `sub` in jq query.
+
"${CURL[@]}" "${REPOLOGY_PROJECT_URI}" \
+
| "${JQ[@]}" '.[] | select(.repo == "aur" and .srcname == "1password-beta") | .version | sub("_"; "-")'
+
)
-
if [[ -f ${JSON_HEAP-} ]]; then
-
rm "${JSON_HEAP}"
+
remote_versions["beta/darwin"]=$(
+
"${CURL[@]}" "${APP_UPDATES_URI_BASE}/Y" \
+
| "${JQ[@]}" 'select(.available == "1") | .version'
+
)
fi
}
-
trap cleanup EXIT
+
render_versions_json() {
+
local key value
-
# Get channel versions from versions.json
-
declare -A versions
-
while IFS='=' read -r key value; do
-
versions["${key}"]="${value}"
-
done < <(jq -r 'to_entries[] | "\(.key)=\(.value)"' versions.json)
+
for key in "${!local_versions[@]}"; do
+
value="${local_versions[${key}]}"
+
echo "${key}"
+
echo "${value}"
+
done \
+
| jq -nR 'reduce inputs as $i ({}; . + { $i: input })'
+
}
-
TMP_GNUPGHOME=$(mktemp -dt 1password-gui.gnupghome.XXXXXX)
-
export GNUPGHOME="${TMP_GNUPGHOME}"
-
gpg --no-default-keyring --keyring trustedkeys.kbx \
-
--keyserver keyserver.ubuntu.com \
-
--receive-keys 3FEF9748469ADBE15DA7CA80AC2D62742012EA22
-
JSON_HEAP=$(mktemp -t 1password-gui.jsonheap.XXXXXX)
-
for channel in stable beta; do
-
for os in linux darwin; do
-
for arch in x86_64 aarch64; do
-
version="${versions[${channel}-${os}]}"
-
url=$(mk_url ${os} ${channel} ${arch} ${version})
-
nix store prefetch-file --json "${url}" | jq "
-
{
-
\"${channel}\": {
-
\"${arch}-${os}\": {
-
\"url\": \"${url}\",
-
\"hash\": .hash,
-
\"storePath\": .storePath
-
}
-
}
-
}" >> "${JSON_HEAP}"
+
cd -- "$(dirname "${BASH_SOURCE[0]}")"
-
# For some reason 1Password PGP signs only Linux binaries.
-
if [[ ${os} == "linux" ]]; then
-
gpgv \
-
$(nix store prefetch-file --json "${url}.sig" | jq -r .storePath) \
-
$(jq -r --slurp ".[-1].[].[].storePath" "${JSON_HEAP}")
-
fi
-
done
-
done
+
attr_path=${UPDATE_NIX_ATTR_PATH}
+
case "${attr_path}" in
+
_1password-gui) channel="stable" ;;
+
_1password-gui-beta) channel="beta" ;;
+
*)
+
echo "Unknown attribute path ${attr_path}" >&2
+
exit 1
+
esac
+
+
declare -A local_versions remote_versions
+
declare -a new_version_available=()
+
read_local_versions "${channel}"
+
read_remote_versions "${channel}"
+
for i in "${!remote_versions[@]}"; do
+
if [[ "${local_versions[$i]}" != "${remote_versions[$i]}" ]]; then
+
old_version="${local_versions[$i]}"
+
new_version="${remote_versions[$i]}"
+
new_version_available+=("$i/$new_version")
+
fi
done
-
# Combine heap of hash+url objects into a single JSON object.
-
jq --slurp 'reduce .[] as $x ({}; . * $x) | del (.[].[].storePath)' "${JSON_HEAP}" > sources.json
+
if [[ ${#new_version_available[@]} -eq 0 ]]; then
+
# up to date
+
exit
+
fi
+
+
./update-sources.py "${new_version_available[@]}"
+
cat <<EOF
+
[
+
{
+
"attrPath": "${attr_path}",
+
"oldVersion": "${old_version}",
+
"newVersion": "${new_version}",
+
"files": [
+
"$PWD/sources.json"
+
]
+
}
+
]
+
EOF
-6
pkgs/by-name/_1/_1password-gui/versions.json
···
-
{
-
"stable-linux": "8.10.78",
-
"stable-darwin": "8.10.78",
-
"beta-linux":"8.10.80-18.BETA",
-
"beta-darwin": "8.10.80-18.BETA"
-
}