Skip to content

Instantly share code, notes, and snippets.

@supermarsx
Created August 27, 2025 17:32
Show Gist options
  • Save supermarsx/be0fcfba150e0e374e4294ee03e0f3a1 to your computer and use it in GitHub Desktop.
Save supermarsx/be0fcfba150e0e374e4294ee03e0f3a1 to your computer and use it in GitHub Desktop.
Wi-Fi QR Forge, create QR codes for Wi-Fi networks fast
#!/usr/bin/env bash
# Wi‑Fi QR Forge — a Wi‑Fi QR code generator with optional password creation
#
# Requirements: qrencode (preferred). Optional: xclip/wl-copy/pbcopy to copy payload to clipboard.
#
# Examples:
# ./wifiqr.sh -s "MySSID" -p "s3cretpass" -t WPA -o ./mywifi.png
# ./wifiqr.sh -s "Café do Bairro" -t nopass -f ansi # print QR to terminal
# ./wifiqr.sh -s Net -g 16 -o wifi.svg -f svg -C # generate 16‑char password, SVG, copy payload
# ./wifiqr.sh -s Net -p pass -o out.png -R # add R:1 (WPA3 transition mode disabled)
# AUTO_INSTALL=1 ./wifiqr.sh -s Net -p pass -o out.png # auto-install qrencode if missing
#
# QR payload format used:
# WIFI:S:<SSID>;T:<WEP|WPA|nopass>;P:<password>;H:<true|false>;R:1;;
# - T:WPA is used for any WPA generation (WPA/WPA2/WPA3).
# - R:1 disables WPA2 transition mode (WPA3 spec). Optional.
# - H: is included only if specified.
set -Eeuo pipefail
# ──────────────────────────────────────────────────────────────────────────────
# Pretty UI helpers
# ──────────────────────────────────────────────────────────────────────────────
if [[ -t 1 ]]; then
BOLD=$(tput bold || true); DIM=$(tput dim || true); RESET=$(tput sgr0 || true)
BLUE=$(tput setaf 6 || true); GREEN=$(tput setaf 2 || true); YELLOW=$(tput setaf 3 || true); RED=$(tput setaf 1 || true)
else
BOLD=""; DIM=""; RESET=""; BLUE=""; GREEN=""; YELLOW=""; RED=""
fi
die() { echo -e "${RED}${BOLD}✖${RESET} $*" >&2; exit 1; }
info() { echo -e "${BLUE}${BOLD}ℹ${RESET} $*"; }
ok() { echo -e "${GREEN}${BOLD}✔${RESET} $*"; }
warn() { echo -e "${YELLOW}${BOLD}!${RESET} $*"; }
banner() {
local title="Wi‑Fi QR Forge"
local line="$(printf '═%.0s' {1..66})"
echo -e "${BLUE}${BOLD}╔$line╗${RESET}"
printf "%s║ %-64s ║%s\n" "${BLUE}${BOLD}" "$title" "${RESET}"
echo -e "${BLUE}${BOLD}╚$line╝${RESET}"
}
# ──────────────────────────────────────────────────────────────────────────────
# Dependency checks / installers
# ──────────────────────────────────────────────────────────────────────────────
have() { command -v "$1" >/dev/null 2>&1; }
pkg_manager=""
detect_pkg_manager() {
for pm in apt-get dnf yum pacman zypper apk brew port; do
if have "$pm"; then pkg_manager="$pm"; return 0; fi
done
return 1
}
install_qrencode() {
detect_pkg_manager || return 1
case "$pkg_manager" in
apt-get) sudo apt-get update && sudo apt-get install -y qrencode ;;
dnf) sudo dnf install -y qrencode ;;
yum) sudo yum install -y qrencode ;;
pacman) sudo pacman -Sy --noconfirm qrencode ;;
zypper) sudo zypper install -y qrencode ;;
apk) sudo apk add --no-cache qrencode ;;
brew) brew install qrencode ;;
port) sudo port install qrencode ;;
*) return 1 ;;
esac
}
ensure_qrencode() {
if have qrencode; then return 0; fi
warn "qrencode not found. It's required to render the QR."
if [[ "${AUTO_INSTALL:-0}" == "1" || "${AUTO_INSTALL:-false}" == "true" || "${DO_INSTALL:-0}" == "1" ]]; then
info "Attempting to install qrencode (package manager: ${pkg_manager:-auto-detect})…"
install_qrencode || die "Unable to install qrencode automatically. Install it manually and retry."
ok "qrencode installed."
else
info "Install suggestions:"
echo " Debian/Ubuntu: sudo apt-get update && sudo apt-get install qrencode"
echo " Fedora/RHEL: sudo dnf install qrencode"
echo " Arch: sudo pacman -S qrencode"
echo " Alpine: sudo apk add qrencode"
echo " macOS: brew install qrencode"
die "Rerun after installing qrencode (or set AUTO_INSTALL=1)."
fi
}
# ──────────────────────────────────────────────────────────────────────────────
# Escaping per ZXing Wi‑Fi QR standard (escape \\ ; , : ")
# ──────────────────────────────────────────────────────────────────────────────
esc() {
local s=$1
s=${s//\\/\\\\}
s=${s//;/\\;}
s=${s//,/\\,}
s=${s//:/\\:}
s=${s//\"/\\\"}
printf '%s' "$s"
}
# ──────────────────────────────────────────────────────────────────────────────
# Password generator (safe ASCII, excludes spaces and problematic shell chars)
# ──────────────────────────────────────────────────────────────────────────────
gen_password() {
local len=${1:-16}
local charset='A-Za-z0-9!@#$%&*+=?^_~.-'
tr -dc "$charset" </dev/urandom | head -c "$len"
}
# ──────────────────────────────────────────────────────────────────────────────
# Usage
# ──────────────────────────────────────────────────────────────────────────────
usage() {
cat <<'USAGE'
Usage: wifiqr.sh -s <SSID> [-p <password>] [-t <WPA|WEP|nopass>] \
[-H true|false] [-R] [-o <file>] [-f png|svg|ansi] \
[-g <len>] [-m <margin>] [-S <scale>] [-C] [--install]
Options:
-s SSID Network name (SSID)
-p PASSWORD Wi‑Fi password (ignored for -t nopass). If omitted and -g is set, a password is generated.
-t TYPE Security type (default: WPA). Use 'nopass' for open networks. WPA covers WPA/WPA2/WPA3.
-H BOOL Hidden SSID (true/false). Omit for visible.
-R Add R:1 tag (WPA3 transition mode disabled). Optional.
-o FILE Output file (default: wifi-<ssid>-<timestamp>.png unless -f ansi).
-f FORMAT Output format: png (default), svg, or ansi (prints to terminal).
-g N Generate a random password of length N.
-m MARGIN Quiet zone (default: 4 modules).
-S SCALE Pixel scale per module (default: 8 for png/svg, 1 for ansi).
-C Copy QR payload string to clipboard (pbcopy/xclip/wl-copy if available).
--install Attempt to auto-install qrencode if missing (or set AUTO_INSTALL=1).
-h, --help Show this help.
Examples:
wifiqr.sh -s "MyHome" -p "CorrectHorseBatteryStaple" -t WPA -o mywifi.png
wifiqr.sh -s "PublicPlaza" -t nopass -f ansi
wifiqr.sh -s OfficeNet -g 20 -o office.svg -f svg -C
wifiqr.sh -s WPA3Net -p supersecure -t WPA -R -o wpa3.png
USAGE
}
# ──────────────────────────────────────────────────────────────────────────────
# Parse args
# ──────────────────────────────────────────────────────────────────────────────
ssid=""; password=""; type="WPA"; hidden=""; REQ_WPA3=0; outfile=""; format="png"; genlen=""; margin=4; scale=""
DO_INSTALL=0
LONGOPTS=("help" "install")
for arg in "$@"; do
case "$arg" in
--help) usage; exit 0 ;;
--install) DO_INSTALL=1 ;;
esac
done
while getopts ":s:p:t:H:R:o:f:g:m:S:C h" opt; do
case "$opt" in
s) ssid=$OPTARG ;;
p) password=$OPTARG ;;
t) type=$OPTARG ;;
H) hidden=$OPTARG ;;
R) REQ_WPA3=1 ;;
o) outfile=$OPTARG ;;
f) format=$OPTARG ;;
g) genlen=$OPTARG ;;
m) margin=$OPTARG ;;
S) scale=$OPTARG ;;
C) COPY=1 ;;
h) usage; exit 0 ;;
:) die "Option -$OPTARG requires an argument. See -h." ;;
\?) die "Unknown option: -$OPTARG. See -h." ;;
esac
done
banner
[[ -z "$ssid" ]] && { usage; die "SSID (-s) is required."; }
# Normalize/validate
shopt -s nocasematch
case "$type" in
wpa|wpa2|wpa3|WPA|WPA2|WPA3) type=WPA ;;
wep|WEP) type=WEP ;;
nopass|NOPASS) type=nopass ;;
*) die "Unsupported type: $type (use WPA/WEP/nopass)" ;;
esac
shopt -u nocasematch
if [[ -n "$genlen" && -z "$password" && "$type" != "nopass" ]]; then
password=$(gen_password "$genlen")
ok "Generated password (${#password} chars)."
fi
if [[ "$type" != "nopass" && -z "$password" ]]; then
warn "No password provided. For open networks use -t nopass; otherwise add -p or -g."
fi
if [[ "$type" == "WPA" ]]; then
if [[ -n "$password" && ${#password} -lt 8 ]]; then
warn "WPA passwords should be at least 8 chars."
fi
fi
# Build payload with escaping
SSID_ESC=$(esc "$ssid")
PAYLOAD="WIFI:S:${SSID_ESC};T:${type};"
if [[ "$type" != "nopass" && -n "$password" ]]; then
PASS_ESC=$(esc "$password")
PAYLOAD+="P:${PASS_ESC};"
fi
if [[ -n "$hidden" ]]; then
case "$hidden" in
true|false|TRUE|FALSE) PAYLOAD+="H:${hidden,,};" ;;
*) warn "-H should be true or false; ignoring." ;;
esac
fi
if [[ "$REQ_WPA3" == 1 ]]; then
PAYLOAD+="R:1;"
fi
PAYLOAD+=";"
# Copy to clipboard if requested
copy_payload() {
if have pbcopy; then printf '%s' "$PAYLOAD" | pbcopy && ok "Payload copied to clipboard (macOS)." && return
fi
if have wl-copy; then printf '%s' "$PAYLOAD" | wl-copy && ok "Payload copied to clipboard (Wayland)." && return
fi
if have xclip; then printf '%s' "$PAYLOAD" | xclip -selection clipboard && ok "Payload copied to clipboard (X11)." && return
fi
warn "Clipboard tool not found (pbcopy/wl-copy/xclip). Skipping copy."
}
[[ "${COPY:-0}" == 1 ]] && copy_payload || true
# Determine defaults for output
if [[ -z "$scale" ]]; then
if [[ "$format" == "ansi" ]]; then scale=1; else scale=8; fi
fi
if [[ -z "$outfile" && "$format" != "ansi" ]]; then
ts=$(date +%Y%m%d-%H%M%S)
safe_ssid=${ssid//[^A-Za-z0-9._-]/_}
ext=$([[ "$format" == "svg" ]] && echo svg || echo png)
outfile="wifi-${safe_ssid}-${ts}.${ext}"
fi
# Render
case "$format" in
ansi)
ensure_qrencode
info "Rendering QR to terminal (ANSI)…"
echo
qrencode -t ANSIUTF8 -m "$margin" -s "$scale" "$PAYLOAD"
echo
;;
png)
ensure_qrencode
info "Rendering PNG → ${outfile}"
qrencode -t PNG -m "$margin" -s "$scale" -o "$outfile" "$PAYLOAD"
ok "Saved ${outfile}"
;;
svg)
ensure_qrencode
info "Rendering SVG → ${outfile}"
qrencode -t SVG -m "$margin" -s "$scale" -o "$outfile" "$PAYLOAD"
ok "Saved ${outfile}"
;;
*) die "Unknown format: $format (use png|svg|ansi)" ;;
esac
# Summary box
summary_line() { printf "%-14s %s\n" "$1" "$2"; }
printf "${DIM}──────────────────────────────────────────────────────────────────${RESET}\n"
summary_line "SSID" "$ssid"
summary_line "Type" "$type"
[[ "$type" != "nopass" && -n "$password" ]] && summary_line "Password" "$password"
[[ -n "$hidden" ]] && summary_line "Hidden" "${hidden,,}"
[[ "$REQ_WPA3" == 1 ]] && summary_line "R" "1"
summary_line "Payload" "$PAYLOAD"
if [[ "$format" != "ansi" ]]; then summary_line "File" "$outfile"; fi
printf "${DIM}──────────────────────────────────────────────────────────────────${RESET}\n"
ok "Done. Point your camera at the QR to join the network."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment