piglet/piglet

616 lines
16 KiB
Bash
Executable File

#!/bin/sh
# piglet - Porkbun DNS API client.
#
# This is free and unencumbered software released into the public domain.
#
# Anyone is free to copy, modify, publish, use, compile, sell, or
# distribute this software, either in source code form or as a compiled
# binary, for any purpose, commercial or non-commercial, and by any
# means.
#
# In jurisdictions that recognize copyright laws, the author or authors
# of this software dedicate any and all copyright interest in the
# software to the public domain. We make this dedication for the benefit
# of the public at large and to the detriment of our heirs and
# successors. We intend this dedication to be an overt act of
# relinquishment in perpetuity of all present and future rights to this
# software under copyright law.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
#
# For more information, please refer to <http://unlicense.org/>
piglet_version='0.2.0'
piglet_conf="${HOME}/.config/piglet.conf"
piglet_conf_dir="${HOME}/.config/piglet"
piglet_conf_alt="$piglet_conf_dir/piglet.conf"
[ -f "$piglet_conf_alt" ] && piglet_conf="$piglet_conf_alt"
print_help() {
cat <<- 'EOF'
Porkbun DNS API client.
/\ ____ /\
\/ \/ <OINK!>
| *(00)* | /
\ /&"
|_|--|_|
Usage: piglet [-cdjhv] command <domain> [arg=value ...]
Options:
-c, --config path to configuration file [default: ~/.config/piglet.conf]
-d, --domain domain name on which operations will be performed.
-j, --json raw JSON output.
-h, --help print this help message and exit.
-v, --version print version and exit.
Commands:
create Create a DNS record.
edit Edit a DNS record by Domain and ID.
delete Delete a specific DNS record by ID.
retrieve Retrieve all DNS records or a single record by ID.
config Setup configuration file.
Use 'piglet command --help' to see detailed help.
For more info, please refference to original API Documentation:
<https://porkbun.com/api/json/v3/documentation>
This software is not affiliated with Porkbun LLC <https://porkbun.com/>
License: The Unlicense <http://unlicense.org/>
EOF
}
print_help_create() {
cat <<- EOF
Create a DNS record.
Usage: piglet [options...] create [arg=value ...]
Arguments:
name (optional)
The subdomain for the record being created, not including
the domain itself. Leave blank to create a record on the root domain.
Use '*' to create a wildcard record.
type
The type of record being created. Valid types are: A, MX, CNAME, ALIAS,
TXT, NS, AAAA, SRV, TLSA, CAA.
content
The answer content for the record.
ttl (optional) [default: 600 seconds]
The time to live in seconds for the record
prio (optional) [default: 0]
The priority of the record for those that support it.
EOF
}
print_help_edit() {
cat <<- EOF
Edit a DNS record by Domain and ID.
Usage: piglet [options...] edit [arg=value ...]
Arguments:
id
ID of DNS record to edit.
name (optional)
The subdomain for the record being created, not including
the domain itself. Leave blank to create a record on the root domain.
Use '*' to create a wildcard record.
type
The type of record being created. Valid types are: A, MX, CNAME, ALIAS,
TXT, NS, AAAA, SRV, TLSA, CAA.
content
The answer content for the record.
ttl (optional) [default: 600 seconds]
The time to live in seconds for the record
prio (optional) [default: 0]
The priority of the record for those that support it.
EOF
}
print_help_delete() {
cat <<- EOF
Delete a specific DNS record by Doman and ID..
Usage: piglet [options...] delete [arg=value ...]
Arguments:
id
DNS record ID.
EOF
}
print_help_retrieve() {
cat <<- EOF
Retrieve all editable DNS records associated with a domain or a single record
for a particular record ID.
Usage: piglet [options...] retrieve [arg=value ...]
Options:
-l, --limit line length limit for table view [default: 36]
Arguments:
id (optional)
DNS record ID.
EOF
}
# ----------------------------------------- #
# API methods #
# ----------------------------------------- #
api_create() {
# Create DNS record.
# See: https://porkbun.com/api/json/v3/documentation#DNS%20Create%20Record
# Usage: api_create domain name type content ttl prio
curl -sS -X POST https://porkbun.com/api/json/v3/dns/create/"$1" \
--data '{
"secretapikey": "'"$Secret_API_Key"'",
"apikey": "'"$API_Key"'",
"name": "'"$2"'",
"type": "'"$3"'",
"content": "'"$4"'",
"ttl": "'"$5"'",
"prio": "'"$6"'"
}'
echo # just print new line
}
api_edit() {
# Edit DNS record by Domain and ID.
# See: https://porkbun.com/api/json/v3/documentation#DNS%20Edit%20Record%20by%20Domain%20and%20ID
# Usage: api_edit doamin id name type content ttl prio
curl -sS -X POST https://porkbun.com/api/json/v3/dns/edit/"$1"/"$2" \
--data '{
"secretapikey": "'"$Secret_API_Key"'",
"apikey": "'"$API_Key"'",
"name": "'"$3"'",
"type": "'"$4"'",
"content": "'"$5"'",
"ttl": "'"$6"'",
"prio": "'"$7"'"
}'
echo # just print new line
}
api_delete() {
# Delete DNS record.
# See: https://porkbun.com/api/json/v3/documentation#DNS%20Delete%20Record%20by%20Domain%20and%20ID
# Usage: api_delete domain id
curl -sS -X POST https://porkbun.com/api/json/v3/dns/delete/"$1"/"$2" \
--data '{
"secretapikey": "'"$Secret_API_Key"'",
"apikey": "'"$API_Key"'"
}'
echo # just print new line
}
api_retrieve() {
# Retrieve DNS records.
# See: https://porkbun.com/api/json/v3/documentation#DNS%20Retrieve%20Records%20by%20Domain%20or%20ID
# Usage: api_retrieve domain id
_target="$1"
if [ "$#" -eq 2 ]; then
target="$1/$2"
fi
curl -sS -X POST https://porkbun.com/api/json/v3/dns/retrieve/"$target" \
--data '{
"secretapikey": "'"$Secret_API_Key"'",
"apikey": "'"$API_Key"'"
}'
echo # just print new line
}
# ----------------------------------------- #
# CLI functions #
# ----------------------------------------- #
_init_piglet() {
# Source configuration file
# shellcheck disable=SC1090
. "$piglet_conf"
if [ -z "$API_Key" ] || [ -z "$Secret_API_Key" ]; then
echo "Bad API credentials. Please check the ${piglet_conf}. " \
"Run 'piglet config' to create new config file." >&2; exit 1
fi
if [ -z "$domain" ]; then
echo 'Domain name is not set' >&2; exit 1;
fi
}
split_name() {
if [ "$(echo "$1" | tr . ' ' | wc -w)" -gt 2 ]; then
name="$(echo "$1" | awk -F. '{NF-=2} 1' | sed 's/ /\./g')"
domain="$(echo "$1" | awk -F. 'END {print $(NF-1), $NF}' | sed 's/ /\./g')"
else
domain="$1"
fi
}
piglet_config() {
mkdir -p "$HOME"/.config
printf '%s\n%s\n' \
"Enter Porkbun DNS API keys. Press ^C to cancel." \
"Help: https://kb.porkbun.com/article/190-getting-started-with-the-porkbun-dns-api"
printf 'API_Key: '; read -r api_key
printf 'Secret_API_Key: '; read -r secret_api_key
mkdir -p "$piglet_conf_dir"
cat > "$piglet_conf_alt" <<- EOF
# Porkbun DNS API credentials
API_Key=$api_key
Secret_API_Key=$secret_api_key
EOF
printf 'Config saved as %s\n' "$piglet_conf_alt"
}
piglet_create() {
while [ "$#" -ne 0 ]; do
case "$1" in
-c|--config|--config=*)
opts "$1" "$2"
piglet_conf="$val"
shift "$sft"
;;
-d|--domain|--domain=*)
opts "$1" "$2"
domain="$val"
shift "$sft"
;;
-j|--json)
raw_json=1
shift;;
-h|--help)
print_help_create
exit 0
;;
name=*)
name="${1#*=}"
shift
;;
type=*)
type="${1#*=}"
shift
;;
content=*)
content="${1#*=}"
shift
;;
ttl=*)
ttl="${1#*=}"
shift
;;
prio=*)
prio="${1#*=}"
shift
;;
-*)
echo "Unknown option: $1" >&2
exit 1
;;
*=*)
echo "Unknown key: $1" >&2
exit 1
;;
*)
split_name "$1"
shift
esac
done
_init_piglet
api_create "$domain" "$name" "$type" "$content" "$ttl" "$prio"
}
piglet_edit() {
while [ "$#" -ne 0 ]; do
case "$1" in
-c|--config|--config=*)
opts "$1" "$2"
piglet_conf="$val"
shift "$sft"
;;
-d|--domain|--domain=*)
opts "$1" "$2"
domain="$val"
shift "$sft"
;;
-j|--json)
raw_json=1
shift
;;
-h|--help)
print_help_edit
exit 0
;;
id=*)
record_id="${1#*=}"
shift
;;
name=*)
name="${1#*=}"
shift
;;
type=*)
type="${1#*=}"
shift
;;
content=*)
content="${1#*=}"
shift
;;
ttl=*)
ttl="${1#*=}"
shift
;;
prio=*)
prio="${1#*=}"
shift
;;
-*)
echo "Unknown option: $1" >&2
exit 1
;;
*=*)
echo "Unknown key: $1" >&2
exit 1
;;
*)
split_name "$1"
shift
esac
done
_init_piglet
api_edit "$domain" "$record_id" "$name" "$type" "$content" "$ttl" "$prio"
}
piglet_delete() {
while [ "$#" -ne 0 ]; do
case "$1" in
-c|--config|--config=*)
opts "$1" "$2"
piglet_conf="$val"
shift "$sft"
;;
-d|--domain|--domain=*)
opts "$1" "$2"
domain="$val"
shift "$sft"
;;
-j|--json)
raw_json=1
shift
;;
-h|--help)
print_help_delete
exit 0
;;
id=*)
record_id="${1#*=}"
shift
;;
-*)
echo "Unknown option: $1" >&2
exit 1
;;
*=*)
echo "Unknown key: $1" >&2
exit 1
;;
*)
split_name "$1"
shift
esac
done
_init_piglet
api_delete "$domain" "$record_id"
}
piglet_retrieve() {
while [ "$#" -ne 0 ]; do
case "$1" in
-c|--config|--config=*)
opts "$1" "$2"
piglet_conf="$val"
shift "$sft"
;;
-d|--domain|--domain=*)
opts "$1" "$2"
domain="$val"
shift "$sft"
;;
-l|--limit|--limit=*)
opts "$1" "$2"
limit="$val"
shift "$sft"
;;
-j|--json)
raw_json=1
shift
;;
-h|--help)
print_help_retrieve
exit 0
;;
id=*)
record_id="${1#*=}"
shift
;;
-*)
echo "Unknown option: $1" >&2
exit 1
;;
*=*)
echo "Unknown key: $1" >&2
exit 1
;;
*)
split_name "$1"
shift
esac
done
_init_piglet
json_out="$(api_retrieve "$domain" "$record_id")"
if ! hash jq 2>/dev/null; then
raw_json=1
echo "Install jq to enable table view." >&2
fi
if [ -n "$raw_json" ]; then
echo "$json_out"
else
# Print table
limit="${limit:-36}"
echo "$json_out" |
jq -r '.records[] | .id,.name,.type,.content,.ttl,.prio' |
awk -v len="$limit" '{if (length >= len) {print substr($0, 0, len) "..."}
else {print $0}} NR%6==0 {print "|"}' |
tr '\n' '#' | tr '|' '\n' | sed 's/^#//g' |
awk 'BEGIN {print "ID#NAME#TYPE#CONTENT#TTL#PRIO"} {print $0}' |
column -t -s '#'
fi
}
# ----------------------------------------- #
# Args parser #
# ----------------------------------------- #
opts() {
# GNU-style CLI options parser.
# Parse --opt VAL and --opt=VAL options.
# Requires 2 arguments: $1, $2.
# Returns:
# $opt - option name.
# $val - option value.
# $sft - value for shift.
opt="${1%%=*}"; val="${1#*=}"; sft=1
if [ "$opt" = "$val" ]; then
if [ -n "$2" ] && [ "$(echo "$2" | cut -c1-1)" != "-" ]; then
val="$2"; sft=2
else
unset val
fi
fi
if [ -z "$val" ]; then
printf 'Missing argument for option %s\n' "$opt" >&2; exit 1
fi
}
if [ "$#" -eq 0 ]; then
print_help
fi
# Split combined short options, e.g. '-abc' to '-a' '-b' '-c'
for args in "$@"; do
shift
case "$args" in
--*)
set -- "$@" "$args"
;; # save long options
-*)
args="$(echo "${args#-}" | grep -o . | xargs -I {} echo -n '-{} ')"
# shellcheck disable=SC2086
set -- "$@" $args
;;
*)
set -- "$@" "$args" # save positional arguments
esac
done
# Final arguments parser
while [ "$#" -ne 0 ]; do
case "$1" in
-c|--config|--config=*)
opts "$1" "$2"
piglet_conf="$val"
shift "$sft"
;;
-d|--domain|--domain=*)
opts "$1" "$2"
domain="$val"
shift "$sft"
;;
-j|--json)
raw_json=1
shift
;;
-h|--help)
print_help
exit 0
;;
-v|--version)
echo $piglet_version
exit 0
;;
create)
shift; piglet_create "$@"
shift "$#"
;;
edit)
shift; piglet_edit "$@"
shift "$#"
;;
delete)
shift; piglet_delete "$@"
shift "$#"
;;
retrieve)
shift; piglet_retrieve "$@"
shift "$#"
;;
config)
piglet_config
exit "$?"
;;
-*)
echo "Unknown option: $1" >&2
exit 1
;;
*)
if echo "$1" | grep -E '.+\..+' >/dev/null 2>&1; then
split_name "$1"
shift
else
echo "Unknown command: $1" >&2
exit 1
fi
esac
done