mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-28 19:42:51 +08:00
217 lines
6.9 KiB
Bash
217 lines
6.9 KiB
Bash
# shellcheck shell=sh
|
|
#
|
|
# gitbackup -- remote URL parsing, provider detection, deploy-key deep links
|
|
# (spec "Парсер remote URL", brief §5.3).
|
|
#
|
|
# Sourced, never executed: nothing here runs at load time. gb_provider reads
|
|
# gitbackup.origin.provider through gb_uci_get (lib.sh), so a caller must
|
|
# source lib.sh first, same convention as device.sh.
|
|
|
|
# gb_parse_url <url>
|
|
#
|
|
# Parses one of the four remote URL forms the spec requires, with or without
|
|
# a trailing ".git":
|
|
# git@host:owner/repo.git scp-like (git's own alias syntax)
|
|
# ssh://[user@]host[:port]/owner/repo.git
|
|
# https://host[:port]/owner/repo.git
|
|
#
|
|
# Prints "scheme host port owner repo" on one line, so a caller does
|
|
# `set -- $(gb_parse_url "$url")`. port is "0" when the URL did not name
|
|
# one -- the parser does not guess a scheme's default port, because what
|
|
# that default even means differs by caller (auth.sh's ssh -p vs.
|
|
# visibility.sh's HTTPS API host, see visibility.sh's _gb_hostport_for_api).
|
|
#
|
|
# Returns 1 and prints nothing on anything that is not one of the three
|
|
# forms above, or where owner/repo fail the charset check -- a mangled URL
|
|
# must fail outright, never be parsed halfway into a wrong-but-plausible
|
|
# result (ticket 03 acceptance criterion).
|
|
gb_parse_url() {
|
|
_gb_u="$1"
|
|
_gb_scheme=''
|
|
_gb_host=''
|
|
_gb_port=''
|
|
_gb_owner=''
|
|
_gb_repo=''
|
|
|
|
case "$_gb_u" in
|
|
https://*)
|
|
_gb_scheme=https
|
|
_gb_rest="${_gb_u#https://}"
|
|
_gb_parse_hostport_path "$_gb_rest" || return 1
|
|
;;
|
|
ssh://*)
|
|
_gb_scheme=ssh
|
|
_gb_rest="${_gb_u#ssh://}"
|
|
case "$_gb_rest" in
|
|
*@*) _gb_rest="${_gb_rest#*@}" ;;
|
|
esac
|
|
_gb_parse_hostport_path "$_gb_rest" || return 1
|
|
;;
|
|
*://*)
|
|
# Some other scheme (git://, ftp://, ...) -- not one of the three
|
|
# forms the spec names, refused rather than guessed at.
|
|
return 1
|
|
;;
|
|
*:*)
|
|
# scp-like alias syntax: "[user@]host:owner/repo(.git)?". Git itself
|
|
# only accepts this when the part before the first ':' has no '/' --
|
|
# that is what tells it apart from a local path or an already-handled
|
|
# scheme above.
|
|
_gb_scheme=ssh
|
|
_gb_port=''
|
|
_gb_userhost="${_gb_u%%:*}"
|
|
case "$_gb_userhost" in
|
|
*/*) return 1 ;;
|
|
esac
|
|
[ -n "$_gb_userhost" ] || return 1
|
|
case "$_gb_userhost" in
|
|
*@*) _gb_host="${_gb_userhost#*@}" ;;
|
|
*) _gb_host="$_gb_userhost" ;;
|
|
esac
|
|
_gb_parse_ownerrepo "${_gb_u#*:}" || return 1
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
[ -n "$_gb_host" ] || return 1
|
|
printf '%s %s %s %s %s\n' "$_gb_scheme" "$_gb_host" "${_gb_port:-0}" "$_gb_owner" "$_gb_repo"
|
|
}
|
|
|
|
# _gb_parse_hostport_path <host[:port]/owner/repo[.git]>
|
|
#
|
|
# Shared by the https:// and ssh:// branches once their scheme prefix (and,
|
|
# for ssh://, an optional user@) is already stripped. Sets _gb_host, _gb_port
|
|
# and, through _gb_parse_ownerrepo, _gb_owner/_gb_repo.
|
|
_gb_parse_hostport_path() {
|
|
_gb_r="$1"
|
|
case "$_gb_r" in
|
|
*/*) ;;
|
|
*) return 1 ;;
|
|
esac
|
|
_gb_hostport="${_gb_r%%/*}"
|
|
_gb_path="${_gb_r#*/}"
|
|
case "$_gb_hostport" in
|
|
*:*)
|
|
_gb_host="${_gb_hostport%%:*}"
|
|
_gb_port="${_gb_hostport#*:}"
|
|
case "$_gb_port" in
|
|
''|*[!0-9]*) return 1 ;;
|
|
esac
|
|
;;
|
|
*)
|
|
_gb_host="$_gb_hostport"
|
|
_gb_port=''
|
|
;;
|
|
esac
|
|
[ -n "$_gb_host" ] || return 1
|
|
_gb_parse_ownerrepo "$_gb_path"
|
|
}
|
|
|
|
# _gb_parse_ownerrepo <owner/repo[.git]> -- sets _gb_owner and _gb_repo.
|
|
#
|
|
# Exactly one '/' is allowed: nested groups ("group/subgroup/repo") are not
|
|
# one of the four supported forms and are refused, not guessed at.
|
|
_gb_parse_ownerrepo() {
|
|
_gb_p="$1"
|
|
case "$_gb_p" in
|
|
*/*/*) return 1 ;;
|
|
*/*) ;;
|
|
*) return 1 ;;
|
|
esac
|
|
_gb_owner="${_gb_p%%/*}"
|
|
_gb_repo="${_gb_p#*/}"
|
|
_gb_repo="${_gb_repo%.git}"
|
|
_gb_charset_ok "$_gb_owner" || return 1
|
|
_gb_charset_ok "$_gb_repo" || return 1
|
|
return 0
|
|
}
|
|
|
|
# _gb_charset_ok <string> -- non-empty and only [A-Za-z0-9._-].
|
|
_gb_charset_ok() {
|
|
[ -n "$1" ] || return 1
|
|
case "$1" in
|
|
*[!A-Za-z0-9._-]*) return 1 ;;
|
|
esac
|
|
return 0
|
|
}
|
|
|
|
# gb_provider <host>
|
|
#
|
|
# github.com/gitlab.com/bitbucket.org/codeberg.org are recognized by host;
|
|
# anything else is "generic" unless gitbackup.origin.provider names one
|
|
# explicitly, which always wins over host detection (ticket 03 acceptance
|
|
# criterion) -- the one escape hatch for a self-hosted GitLab/Gitea/Forgejo
|
|
# instance, which cannot be told apart from an unknown host by name alone.
|
|
#
|
|
# codeberg.org maps to "gitea": Codeberg runs Forgejo, whose anonymous repo
|
|
# API is the same shape as Gitea's (spec, "Проверенные факты 25.12.4 →
|
|
# Провайдеры"), and gitbackup.origin.provider's enum has no separate
|
|
# "codeberg"/"forgejo" value to return instead.
|
|
gb_provider() {
|
|
_gb_host="$1"
|
|
_gb_opt=$(gb_uci_get gitbackup.origin.provider auto)
|
|
case "$_gb_opt" in
|
|
auto) ;;
|
|
*) printf '%s\n' "$_gb_opt"; return 0 ;;
|
|
esac
|
|
case "$_gb_host" in
|
|
github.com) printf 'github\n' ;;
|
|
gitlab.com) printf 'gitlab\n' ;;
|
|
bitbucket.org) printf 'bitbucket\n' ;;
|
|
codeberg.org) printf 'gitea\n' ;;
|
|
*) printf 'generic\n' ;;
|
|
esac
|
|
}
|
|
|
|
# gb_deeplink <provider> <scheme> <host> <owner> <repo>
|
|
#
|
|
# A URL to the provider's "add deploy key" page, or -- for generic, which has
|
|
# no such page anywhere -- an instruction to add the key to authorized_keys
|
|
# by hand (brief §5.3).
|
|
#
|
|
# GitHub and Bitbucket are hardcoded to their one cloud host: gb_provider
|
|
# only ever returns "github"/"bitbucket" for github.com/bitbucket.org, so
|
|
# there is no self-hosted variant to build a different host for. GitLab and
|
|
# Gitea/Forgejo are commonly self-hosted, so their link is built from the
|
|
# remote's own host instead -- the brief's table shows a gitlab.com example,
|
|
# but GitLab's repository-settings page lives at the same path on every
|
|
# instance, not just gitlab.com. <scheme> is coerced to https: a deploy-key
|
|
# page is only ever reachable over the web, but the parsed scheme is often
|
|
# "ssh" (the git remote's own transport, from an scp-like or ssh:// URL),
|
|
# which is never a valid scheme for a browser link.
|
|
gb_deeplink() {
|
|
_gb_p="$1"
|
|
_gb_sch="$2"
|
|
_gb_h="$3"
|
|
_gb_o="$4"
|
|
_gb_r="$5"
|
|
case "$_gb_sch" in
|
|
http|https) ;;
|
|
*) _gb_sch=https ;;
|
|
esac
|
|
case "$_gb_p" in
|
|
github)
|
|
# VERIFY: an anonymous request to /settings/keys/new returns 404 for
|
|
# both a real and a garbage owner/repo (spec, "Проверенные факты"),
|
|
# so unlike the other three this exact path could not be confirmed
|
|
# by the same probe -- it is GitHub's documented deploy-key URL, not
|
|
# a measured one.
|
|
printf 'https://github.com/%s/%s/settings/keys/new\n' "$_gb_o" "$_gb_r"
|
|
;;
|
|
gitlab)
|
|
printf '%s://%s/%s/%s/-/settings/repository\n' "$_gb_sch" "$_gb_h" "$_gb_o" "$_gb_r"
|
|
;;
|
|
gitea)
|
|
printf '%s://%s/%s/%s/settings/keys\n' "$_gb_sch" "$_gb_h" "$_gb_o" "$_gb_r"
|
|
;;
|
|
bitbucket)
|
|
printf 'https://bitbucket.org/%s/%s/admin/access-keys/\n' "$_gb_o" "$_gb_r"
|
|
;;
|
|
*)
|
|
printf 'No provider API to link to; add the deploy key to ~/.ssh/authorized_keys for the git user on %s, ideally with restrict,command="git-shell".\n' "$_gb_h"
|
|
;;
|
|
esac
|
|
}
|