Compare commits

...

7 Commits
v5.06 ... v5.07

Author SHA1 Message Date
Kroese
5332d387f4 fix: Set file attribute (#488)
* fix: Set file attribute
2023-12-24 02:41:44 +01:00
Kroese
f0d08ef263 fix: Remove flag (#487)
* fix: Remove flag
2023-12-23 22:58:45 +01:00
Kroese
e4334f9499 fix: Ignore mknod errors (#486) 2023-12-23 22:28:25 +01:00
Kroese
627ec56262 feat: LINUX_IMMUTABLE flag (#485)
* feat: LINUX_IMMUTABLE flag
2023-12-23 21:26:23 +01:00
Kroese
251cf8121e feat: Add LINUX_IMMUTABLE flag (#484)
To change directory attributes on COW filesystems
2023-12-23 20:35:06 +01:00
Kroese
87fad1b0e9 feat: Set directory attributes (#483)
* feat: Set directory attributes
2023-12-23 20:01:27 +01:00
Kroese
698516ac8c feat: Detect country from timezone (#482) 2023-12-23 18:46:14 +01:00
5 changed files with 126 additions and 71 deletions

View File

@@ -14,7 +14,7 @@ services:
device_cgroup_rules: device_cgroup_rules:
- 'c *:* rwm' - 'c *:* rwm'
cap_add: cap_add:
- NET_ADMIN - NET_ADMIN
ports: ports:
- 5000:5000 - 5000:5000
volumes: volumes:

View File

@@ -77,15 +77,28 @@ getSize() {
esac esac
} }
isCow() {
local FS=$1
if [[ "${FS,,}" == "xfs" || "${FS,,}" == "zfs" || "${FS,,}" == "btrfs" || "${FS,,}" == "bcachefs" ]]; then
return 0
fi
return 1
}
createDisk() { createDisk() {
local DISK_FILE=$1 local DISK_FILE=$1
local DISK_SPACE=$2 local DISK_SPACE=$2
local DISK_DESC=$3 local DISK_DESC=$3
local DISK_FMT=$4 local DISK_FMT=$4
local DATA_SIZE DIR SPACE local FS=$5
local DATA_SIZE DIR SPACE FA
DATA_SIZE=$(numfmt --from=iec "$DISK_SPACE") DATA_SIZE=$(numfmt --from=iec "$DISK_SPACE")
rm -f "$DISK_FILE"
if [[ "$ALLOCATE" != [Nn]* ]]; then if [[ "$ALLOCATE" != [Nn]* ]]; then
# Check free diskspace # Check free diskspace
@@ -104,8 +117,20 @@ createDisk() {
case "${DISK_FMT,,}" in case "${DISK_FMT,,}" in
raw) raw)
if [[ "$ALLOCATE" == [Nn]* ]]; then
if isCow "$FS"; then
if ! touch "$DISK_FILE"; then
error "$FAIL" && exit 77
fi
{ chattr +C "$DISK_FILE"; } || :
FA=$(lsattr "$DISK_FILE")
if [[ "$FA" != *"C"* ]]; then
error "Failed to disable COW for $DISK_DESC image $DISK_FILE on ${FS^^} filesystem (returned $FA)"
fi
fi
if [[ "$ALLOCATE" == [Nn]* ]]; then
# Create an empty file # Create an empty file
if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
rm -f "$DISK_FILE" rm -f "$DISK_FILE"
@@ -125,12 +150,23 @@ createDisk() {
fi fi
;; ;;
qcow2) qcow2)
local DISK_OPTS="$DISK_ALLOC"
[ -n "$DISK_FLAGS" ] && DISK_OPTS="$DISK_OPTS,$DISK_FLAGS" local DISK_PARAM="$DISK_ALLOC"
if ! qemu-img create -f "$DISK_FMT" -o "$DISK_OPTS" -- "$DISK_FILE" "$DATA_SIZE" ; then isCow "$FS" && DISK_PARAM="$DISK_PARAM,nocow=on"
[ -n "$DISK_FLAGS" ] && DISK_PARAM="$DISK_PARAM,$DISK_FLAGS"
if ! qemu-img create -f "$DISK_FMT" -o "$DISK_PARAM" -- "$DISK_FILE" "$DATA_SIZE" ; then
rm -f "$DISK_FILE" rm -f "$DISK_FILE"
error "$FAIL" && exit 70 error "$FAIL" && exit 70
fi fi
if isCow "$FS"; then
FA=$(lsattr "$DISK_FILE")
if [[ "$FA" != *"C"* ]]; then
error "Failed to disable COW for $DISK_DESC image $DISK_FILE on ${FS^^} filesystem (returned $FA)"
fi
fi
;; ;;
esac esac
@@ -142,6 +178,7 @@ resizeDisk() {
local DISK_SPACE=$2 local DISK_SPACE=$2
local DISK_DESC=$3 local DISK_DESC=$3
local DISK_FMT=$4 local DISK_FMT=$4
local FS=$5
local CUR_SIZE DATA_SIZE DIR SPACE local CUR_SIZE DATA_SIZE DIR SPACE
CUR_SIZE=$(getSize "$DISK_FILE") CUR_SIZE=$(getSize "$DISK_FILE")
@@ -168,6 +205,7 @@ resizeDisk() {
case "${DISK_FMT,,}" in case "${DISK_FMT,,}" in
raw) raw)
if [[ "$ALLOCATE" == [Nn]* ]]; then if [[ "$ALLOCATE" == [Nn]* ]]; then
# Resize file by changing its length # Resize file by changing its length
@@ -187,9 +225,11 @@ resizeDisk() {
fi fi
;; ;;
qcow2) qcow2)
if ! qemu-img resize -f "$DISK_FMT" "--$DISK_ALLOC" "$DISK_FILE" "$DATA_SIZE" ; then if ! qemu-img resize -f "$DISK_FMT" "--$DISK_ALLOC" "$DISK_FILE" "$DATA_SIZE" ; then
error "$FAIL" && exit 72 error "$FAIL" && exit 72
fi fi
;; ;;
esac esac
@@ -203,16 +243,18 @@ convertDisk() {
local DST_FMT=$4 local DST_FMT=$4
local DISK_BASE=$5 local DISK_BASE=$5
local DISK_DESC=$6 local DISK_DESC=$6
local CONV_FLAGS="-p" local FS=$7
local DISK_OPTS="$DISK_ALLOC"
local TMP_FILE="$DISK_BASE.tmp"
local DIR CUR_SIZE SPACE
[ -f "$DST_FILE" ] && error "Conversion failed, destination file $DST_FILE already exists?" && exit 79 [ -f "$DST_FILE" ] && error "Conversion failed, destination file $DST_FILE already exists?" && exit 79
[ ! -f "$SOURCE_FILE" ] && error "Conversion failed, source file $SOURCE_FILE does not exists?" && exit 79 [ ! -f "$SOURCE_FILE" ] && error "Conversion failed, source file $SOURCE_FILE does not exists?" && exit 79
local TMP_FILE="$DISK_BASE.tmp"
rm -f "$TMP_FILE"
if [[ "$ALLOCATE" != [Nn]* ]]; then if [[ "$ALLOCATE" != [Nn]* ]]; then
local DIR CUR_SIZE SPACE
# Check free diskspace # Check free diskspace
DIR=$(dirname "$TMP_FILE") DIR=$(dirname "$TMP_FILE")
CUR_SIZE=$(getSize "$SOURCE_FILE") CUR_SIZE=$(getSize "$SOURCE_FILE")
@@ -227,26 +269,29 @@ convertDisk() {
info "Converting $DISK_DESC to $DST_FMT, please wait until completed..." info "Converting $DISK_DESC to $DST_FMT, please wait until completed..."
local CONV_FLAGS="-p"
local DISK_PARAM="$DISK_ALLOC"
isCow "$FS" && DISK_PARAM="$DISK_PARAM,nocow=on"
if [[ "$DST_FMT" != "raw" ]]; then if [[ "$DST_FMT" != "raw" ]]; then
if [[ "$ALLOCATE" == [Nn]* ]]; then if [[ "$ALLOCATE" == [Nn]* ]]; then
CONV_FLAGS="$CONV_FLAGS -c" CONV_FLAGS="$CONV_FLAGS -c"
fi fi
[ -n "$DISK_FLAGS" ] && DISK_OPTS="$DISK_OPTS,$DISK_FLAGS" [ -n "$DISK_FLAGS" ] && DISK_PARAM="$DISK_PARAM,$DISK_FLAGS"
fi fi
rm -f "$TMP_FILE"
# shellcheck disable=SC2086 # shellcheck disable=SC2086
if ! qemu-img convert -f "$SOURCE_FMT" $CONV_FLAGS -o "$DISK_OPTS" -O "$DST_FMT" -- "$SOURCE_FILE" "$TMP_FILE"; then if ! qemu-img convert -f "$SOURCE_FMT" $CONV_FLAGS -o "$DISK_PARAM" -O "$DST_FMT" -- "$SOURCE_FILE" "$TMP_FILE"; then
rm -f "$TMP_FILE" rm -f "$TMP_FILE"
error "Failed to convert $DISK_TYPE $DISK_DESC image to $DST_FMT format in $DIR, is there enough space available?" && exit 79 error "Failed to convert $DISK_TYPE $DISK_DESC image to $DST_FMT format in $DIR, is there enough space available?" && exit 79
fi fi
if [[ "$DST_FMT" == "raw" ]]; then if [[ "$DST_FMT" == "raw" ]]; then
if [[ "$ALLOCATE" != [Nn]* ]]; then if [[ "$ALLOCATE" != [Nn]* ]]; then
# Work around qemu-img bug
CUR_SIZE=$(stat -c%s "$TMP_FILE") CUR_SIZE=$(stat -c%s "$TMP_FILE")
if ! fallocate -l "$CUR_SIZE" "$TMP_FILE"; then if ! fallocate -l "$CUR_SIZE" "$TMP_FILE"; then
info "Failed to allocate $CUR_SIZE bytes for $TMP_FILE" error "Failed to allocate $CUR_SIZE bytes for $DISK_DESC image $TMP_FILE"
fi fi
fi fi
fi fi
@@ -254,44 +299,37 @@ convertDisk() {
rm -f "$SOURCE_FILE" rm -f "$SOURCE_FILE"
mv "$TMP_FILE" "$DST_FILE" mv "$TMP_FILE" "$DST_FILE"
if isCow "$FS"; then
FA=$(lsattr "$DST_FILE")
if [[ "$FA" != *"C"* ]]; then
error "Failed to disable COW for $DISK_DESC image $DST_FILE on ${FS^^} filesystem (returned $FA)"
fi
fi
info "Conversion of $DISK_DESC to $DST_FMT completed succesfully!" info "Conversion of $DISK_DESC to $DST_FMT completed succesfully!"
return 0 return 0
} }
checkFS () { checkFS () {
local DISK_FILE=$1 local FS=$1
local DIR FS FA local DISK_FILE=$2
local DISK_DESC=$3
local DIR FA
DIR=$(dirname "$DISK_FILE") DIR=$(dirname "$DISK_FILE")
[ ! -d "$DIR" ] && return 0 [ ! -d "$DIR" ] && return 0
FS=$(stat -f -c %T "$DIR") if [[ "${FS,,}" == "overlay"* ]]; then
if [[ "$FS" == "overlay"* ]]; then
info "Warning: the filesystem of $DIR is OverlayFS, this usually means it was binded to an invalid path!" info "Warning: the filesystem of $DIR is OverlayFS, this usually means it was binded to an invalid path!"
fi fi
if [[ "$FS" == "xfs" || "$FS" == "zfs" || "$FS" == "btrfs" || "$FS" == "bcachefs" ]]; then if isCow "$FS"; then
local FLAG="nocow" if [ -f "$DISK_FILE" ]; then
if [[ "$DISK_FLAGS" != *"$FLAG="* ]]; then
if [ -z "$DISK_FLAGS" ]; then
DISK_FLAGS="$FLAG=on"
else
DISK_FLAGS="$DISK_FLAGS,$FLAG=on"
fi
fi
if [ -f "$DISK_FILE" ] ; then
FA=$(lsattr "$DISK_FILE") FA=$(lsattr "$DISK_FILE")
[[ "$FA" == *"C"* ]] && FA=$(lsattr -d "$DIR") if [[ "$FA" != *"C"* ]]; then
else info "Warning: COW (copy on write) is not disabled for the $DISK_DESC image file $DISK_FILE, this is recommended on ${FS^^} filesystems!"
FA=$(lsattr -d "$DIR") fi
fi
if [[ "$FA" != *"C"* ]]; then
info "Warning: the filesystem of $DIR is ${FS^^}, and COW (copy on write) is not disabled for that folder!"
info "This will negatively affect performance, please empty the folder and disable COW (chattr +C <path>)."
fi fi
fi fi
@@ -308,7 +346,7 @@ addDisk () {
local DISK_ADDRESS=$7 local DISK_ADDRESS=$7
local DISK_FMT=$8 local DISK_FMT=$8
local DISK_FILE="$DISK_BASE.$DISK_EXT" local DISK_FILE="$DISK_BASE.$DISK_EXT"
local DIR DATA_SIZE PREV_FMT PREV_EXT CUR_SIZE local DIR DATA_SIZE FS PREV_FMT PREV_EXT CUR_SIZE
DIR=$(dirname "$DISK_FILE") DIR=$(dirname "$DISK_FILE")
[ ! -d "$DIR" ] && return 0 [ ! -d "$DIR" ] && return 0
@@ -325,7 +363,8 @@ addDisk () {
fi fi
fi fi
checkFS "$DISK_FILE" || exit $? FS=$(stat -f -c %T "$DIR")
checkFS "$FS" "$DISK_FILE" "$DISK_DESC" || exit $?
if ! [ -f "$DISK_FILE" ] ; then if ! [ -f "$DISK_FILE" ] ; then
@@ -337,7 +376,7 @@ addDisk () {
PREV_EXT="$(fmt2ext "$PREV_FMT")" PREV_EXT="$(fmt2ext "$PREV_FMT")"
if [ -f "$DISK_BASE.$PREV_EXT" ] ; then if [ -f "$DISK_BASE.$PREV_EXT" ] ; then
convertDisk "$DISK_BASE.$PREV_EXT" "$PREV_FMT" "$DISK_FILE" "$DISK_FMT" "$DISK_BASE" "$DISK_DESC" || exit $? convertDisk "$DISK_BASE.$PREV_EXT" "$PREV_FMT" "$DISK_FILE" "$DISK_FMT" "$DISK_BASE" "$DISK_DESC" "$FS" || exit $?
fi fi
fi fi
@@ -346,12 +385,12 @@ addDisk () {
CUR_SIZE=$(getSize "$DISK_FILE") CUR_SIZE=$(getSize "$DISK_FILE")
if (( DATA_SIZE > CUR_SIZE )); then if (( DATA_SIZE > CUR_SIZE )); then
resizeDisk "$DISK_FILE" "$DISK_SPACE" "$DISK_DESC" "$DISK_FMT" || exit $? resizeDisk "$DISK_FILE" "$DISK_SPACE" "$DISK_DESC" "$DISK_FMT" "$FS" || exit $?
fi fi
else else
createDisk "$DISK_FILE" "$DISK_SPACE" "$DISK_DESC" "$DISK_FMT" || exit $? createDisk "$DISK_FILE" "$DISK_SPACE" "$DISK_DESC" "$DISK_FMT" "$FS" || exit $?
fi fi

View File

@@ -51,26 +51,18 @@ MIN_ROOT=471859200
MIN_SPACE=6442450944 MIN_SPACE=6442450944
FS=$(stat -f -c %T "$STORAGE") FS=$(stat -f -c %T "$STORAGE")
if [[ "$FS" == "overlay"* ]]; then if [[ "${FS,,}" == "overlay"* ]]; then
info "Warning: the filesystem of $STORAGE is OverlayFS, this usually means it was binded to an invalid path!" info "Warning: the filesystem of $STORAGE is OverlayFS, this usually means it was binded to an invalid path!"
fi fi
if [[ "$FS" == "xfs" || "$FS" == "zfs" || "$FS" == "btrfs" || "$FS" == "bcachefs" ]]; then
FA=$(lsattr -d "$STORAGE")
if [[ "$FA" != *"C"* ]]; then
info "Warning: the filesystem of $STORAGE is ${FS^^}, and COW (copy on write) is not disabled for that folder!"
info "This will negatively affect performance, please empty the folder and disable COW (chattr +C <path>)."
fi
fi
if [[ "$FS" != "fat"* && "$FS" != "vfat"* && "$FS" != "exfat"* && \ if [[ "${FS,,}" != "fat"* && "${FS,,}" != "vfat"* && "${FS,,}" != "exfat"* && \
"$FS" != "ntfs"* && "$FS" != "fuse"* && "$FS" != "msdos"* ]]; then "${FS,,}" != "ntfs"* && "${FS,,}" != "fuse"* && "${FS,,}" != "msdos"* ]]; then
TMP="$STORAGE/tmp" TMP="$STORAGE/tmp"
else else
TMP="/tmp/dsm" TMP="/tmp/dsm"
SPACE=$(df --output=avail -B 1 /tmp | tail -n 1) SPACE=$(df --output=avail -B 1 /tmp | tail -n 1)
if (( MIN_SPACE > SPACE )); then if (( MIN_SPACE > SPACE )); then
TMP="$STORAGE/tmp" error "The ${FS^^} filesystem of $STORAGE does not support UNIX permissions, and no space left in container!" && exit 93
info "Warning: the $FS filesystem of $STORAGE does not support UNIX permissions.."
fi fi
fi fi
@@ -80,15 +72,9 @@ rm -rf "$TMP" && mkdir -p "$TMP"
SPACE=$(df --output=avail -B 1 / | tail -n 1) SPACE=$(df --output=avail -B 1 / | tail -n 1)
(( MIN_ROOT > SPACE )) && error "Not enough free space in container root, need at least 450 MB available." && exit 96 (( MIN_ROOT > SPACE )) && error "Not enough free space in container root, need at least 450 MB available." && exit 96
SPACE=$(df --output=avail -B 1 "$TMP" | tail -n 1) SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
SPACE_GB=$(( (SPACE + 1073741823)/1073741824 )) SPACE_GB=$(( (SPACE + 1073741823)/1073741824 ))
(( MIN_SPACE > SPACE )) && error "Not enough free space for installation in $STORAGE, have $SPACE_GB GB available but need at least 6 GB." && exit 95 (( MIN_SPACE > SPACE )) && error "Not enough free space for installation in $STORAGE, have $SPACE_GB GB available but need at least 6 GB." && exit 94
if [[ "$TMP" != "$STORAGE/tmp" ]]; then
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
SPACE_GB=$(( (SPACE + 1073741823)/1073741824 ))
(( MIN_SPACE > SPACE )) && error "Not enough free space for installation in $STORAGE, have $SPACE_GB GB available but need at least 6 GB." && exit 94
fi
# Check if output is to interactive TTY # Check if output is to interactive TTY
if [ -t 1 ]; then if [ -t 1 ]; then
@@ -234,15 +220,29 @@ unzip -q -o "$BOOT".zip -d "$TMP"
SYSTEM="$TMP/sys.img" SYSTEM="$TMP/sys.img"
SYSTEM_SIZE=4954537983 SYSTEM_SIZE=4954537983
rm -f "$SYSTEM"
# Check free diskspace # Check free diskspace
SPACE=$(df --output=avail -B 1 "$TMP" | tail -n 1) SPACE=$(df --output=avail -B 1 "$TMP" | tail -n 1)
SPACE_GB=$(( (SPACE + 1073741823)/1073741824 )) SPACE_GB=$(( (SPACE + 1073741823)/1073741824 ))
(( SYSTEM_SIZE > SPACE )) && error "Not enough free space to create a 4 GB system disk, have only $SPACE_GB GB available." && exit 97 (( SYSTEM_SIZE > SPACE )) && error "Not enough free space to create a 4 GB system disk, have only $SPACE_GB GB available." && exit 97
if ! touch "$SYSTEM"; then
error "Could not create file $SYSTEM for the system disk." && exit 98
fi
if [[ "${FS,,}" == "xfs" || "${FS,,}" == "zfs" || "${FS,,}" == "btrfs" || "${FS,,}" == "bcachefs" ]]; then
{ chattr +C "$SYSTEM"; } || :
FA=$(lsattr "$SYSTEM")
if [[ "$FA" != *"C"* ]]; then
error "Failed to disable COW for system image $SYSTEM on ${FS^^} filesystem (returned $FA)"
fi
fi
if ! fallocate -l "$SYSTEM_SIZE" "$SYSTEM"; then if ! fallocate -l "$SYSTEM_SIZE" "$SYSTEM"; then
if ! truncate -s "$SYSTEM_SIZE" "$SYSTEM"; then if ! truncate -s "$SYSTEM_SIZE" "$SYSTEM"; then
rm -f "$SYSTEM" && error "Could not allocate a file for the system disk." && exit 98 rm -f "$SYSTEM"
error "Could not allocate file $SYSTEM for the system disk." && exit 98
fi fi
fi fi
@@ -251,7 +251,11 @@ fi
# Check the filesize # Check the filesize
SIZE=$(stat -c%s "$SYSTEM") SIZE=$(stat -c%s "$SYSTEM")
[[ SIZE -ne SYSTEM_SIZE ]] && rm -f "$SYSTEM" && error "System disk has the wrong size: $SIZE" && exit 90
if [[ SIZE -ne SYSTEM_SIZE ]]; then
rm -f "$SYSTEM"
error "System disk has the wrong size: $SIZE vs $SYSTEM_SIZE" && exit 90
fi
PART="$TMP/partition.fdisk" PART="$TMP/partition.fdisk"

View File

@@ -183,16 +183,20 @@ closeNetwork () {
# Create the necessary file structure for /dev/net/tun # Create the necessary file structure for /dev/net/tun
if [ ! -c /dev/net/tun ]; then if [ ! -c /dev/net/tun ]; then
[ ! -d /dev/net ] && mkdir -m 755 /dev/net [ ! -d /dev/net ] && mkdir -m 755 /dev/net
mknod /dev/net/tun c 10 200 if mknod /dev/net/tun c 10 200; then
chmod 666 /dev/net/tun chmod 666 /dev/net/tun
fi
fi fi
[ ! -c /dev/net/tun ] && error "TUN network interface not available..." && exit 25 if [ ! -c /dev/net/tun ]; then
error "Please add the following docker settings to your container: --device=/dev/net/tun" && exit 25
fi
# Create the necessary file structure for /dev/vhost-net # Create the necessary file structure for /dev/vhost-net
if [ ! -c /dev/vhost-net ]; then if [ ! -c /dev/vhost-net ]; then
mknod /dev/vhost-net c 10 238 if mknod /dev/vhost-net c 10 238; then
chmod 660 /dev/vhost-net chmod 660 /dev/vhost-net
fi
fi fi
update-alternatives --set iptables /usr/sbin/iptables-legacy > /dev/null update-alternatives --set iptables /usr/sbin/iptables-legacy > /dev/null

View File

@@ -11,6 +11,7 @@ trap 'error "Status $? while: $BASH_COMMAND (line $LINENO/$BASH_LINENO)"' ERR
# Docker environment variables # Docker environment variables
: ${TZ:=''} # System local timezone
: ${GPU:='N'} # Disable GPU passthrough : ${GPU:='N'} # Disable GPU passthrough
: ${KVM:='Y'} # Enable KVM acceleration : ${KVM:='Y'} # Enable KVM acceleration
: ${DEBUG:='N'} # Disable debugging mode : ${DEBUG:='N'} # Disable debugging mode
@@ -70,6 +71,13 @@ getCountry () {
setCountry () { setCountry () {
[[ "${TZ,,}" == "asia/harbin" ]] && COUNTRY="CN"
[[ "${TZ,,}" == "asia/beijing" ]] && COUNTRY="CN"
[[ "${TZ,,}" == "asia/urumqi" ]] && COUNTRY="CN"
[[ "${TZ,,}" == "asia/kashgar" ]] && COUNTRY="CN"
[[ "${TZ,,}" == "asia/shanghai" ]] && COUNTRY="CN"
[[ "${TZ,,}" == "asia/chongqing" ]] && COUNTRY="CN"
[ -z "$COUNTRY" ] && getCountry "https://api.ipapi.is" ".location.country_code" [ -z "$COUNTRY" ] && getCountry "https://api.ipapi.is" ".location.country_code"
[ -z "$COUNTRY" ] && getCountry "https://ifconfig.co/json" ".country_iso" [ -z "$COUNTRY" ] && getCountry "https://ifconfig.co/json" ".country_iso"
[ -z "$COUNTRY" ] && getCountry "https://ipinfo.io/json" ".country" [ -z "$COUNTRY" ] && getCountry "https://ipinfo.io/json" ".country"