\(.username) | Exp: \(.expiry) | Logins: \(.max_logins)"' \
2>/dev/null)
fi
if [[ -z "${user_list}" ]]; then
tg_send_msg "${chat_id}" "📋 No users found."
else
tg_send_msg "${chat_id}" "📋 User List:
${user_list}"
fi
}
handle_online() {
local chat_id="$1"
local callback_id="$2"
[[ -n "${callback_id}" ]] && tg_answer_callback "${callback_id}" "Checking online users..."
local online_list
online_list=$(who | grep pts | awk '{print "• "$1" ("$2")"}')
if [[ -z "${online_list}" ]]; then
tg_send_msg "${chat_id}" "👥 No users currently connected."
else
tg_send_msg "${chat_id}" "👥 Online Users:
${online_list}"
fi
}
handle_status() {
local chat_id="$1"
local callback_id="$2"
[[ -n "${callback_id}" ]] && tg_answer_callback "${callback_id}" "Checking status..."
local result
result=$(iranux /status --json 2>/dev/null)
local proxy_st badvpn_st uptime_val
if echo "${result}" | jq -e '.status == "success"' > /dev/null 2>&1; then
proxy_st=$(echo "${result}" | jq -r '.data.proxy_status // "unknown"')
badvpn_st=$(echo "${result}" | jq -r '.data.badvpn_status // "unknown"')
uptime_val=$(echo "${result}" | jq -r '.data.uptime // "unknown"')
else
proxy_st=$(systemctl is-active iranux-tunnel 2>/dev/null || echo "inactive")
badvpn_st=$(systemctl is-active badvpn 2>/dev/null || echo "inactive")
uptime_val=$(uptime -p 2>/dev/null || echo "unknown")
fi
tg_send_msg "${chat_id}" "📊 Server Status
Domain: ${DOMAIN}
SSH Port: 22
Proxy (443): ${proxy_st}
UDPGW (${BADVPN_PORT}): ${badvpn_st}
Uptime: ${uptime_val}"
}
handle_userinfo() {
local chat_id="$1"
local callback_id="$2"
tg_answer_callback "${callback_id}" "User Info"
[[ ! "${chat_id}" =~ ^-?[0-9]+$ ]] && return
rm -f "${STATE_DIR}/state.${chat_id}" "${STATE_DIR}/data.${chat_id}"
echo "userinfo_username" > "${STATE_DIR}/state.${chat_id}"
tg_send_force_reply "${chat_id}" "ℹ️ User Info
Enter the username to look up:"
}
handle_changepass() {
local chat_id="$1"
local callback_id="$2"
tg_answer_callback "${callback_id}" "Change Password"
[[ ! "${chat_id}" =~ ^-?[0-9]+$ ]] && return
rm -f "${STATE_DIR}/state.${chat_id}" "${STATE_DIR}/data.${chat_id}"
echo "changepass_username" > "${STATE_DIR}/state.${chat_id}"
tg_send_force_reply "${chat_id}" "🔑 Change Password
Enter the username:"
}
handle_changelimit() {
local chat_id="$1"
local callback_id="$2"
tg_answer_callback "${callback_id}" "Change Limit"
[[ ! "${chat_id}" =~ ^-?[0-9]+$ ]] && return
rm -f "${STATE_DIR}/state.${chat_id}" "${STATE_DIR}/data.${chat_id}"
echo "changelimit_username" > "${STATE_DIR}/state.${chat_id}"
tg_send_force_reply "${chat_id}" "🔢 Change Max Logins
Enter the username:"
}
handle_changeexpiry() {
local chat_id="$1"
local callback_id="$2"
tg_answer_callback "${callback_id}" "Change Expiry"
[[ ! "${chat_id}" =~ ^-?[0-9]+$ ]] && return
rm -f "${STATE_DIR}/state.${chat_id}" "${STATE_DIR}/data.${chat_id}"
echo "changeexpiry_username" > "${STATE_DIR}/state.${chat_id}"
tg_send_force_reply "${chat_id}" "📅 Change Expiry
Enter the username:"
}
handle_removeexpired() {
local chat_id="$1"
local callback_id="$2"
[[ -n "${callback_id}" ]] && tg_answer_callback "${callback_id}" "Removing expired users..."
local removed=0
local removed_list=""
while IFS=: read -r uname _ uid _; do
if [[ "$uid" -ge 1000 && "$uname" != "nobody" ]]; then
local u_exp
u_exp=$(chage -l "$uname" 2>/dev/null | grep "Account expires" | cut -d: -f2 | xargs)
if [[ -n "$u_exp" && "$u_exp" != "never" && "$u_exp" != "Never" ]]; then
local exp_ts now_ts
exp_ts=$(date -d "$u_exp" +%s 2>/dev/null || echo 0)
now_ts=$(date +%s)
if [[ "$exp_ts" -gt 0 && "$exp_ts" -lt "$now_ts" ]]; then
userdel -r "$uname" 2>/dev/null
sed -i "/^${uname}[[:space:]]\+soft[[:space:]]\+maxlogins/d" /etc/security/limits.conf
removed=$((removed + 1))
removed_list="${removed_list}• ${uname}
"
fi
fi
fi
done < /etc/passwd
if [[ "${removed}" -eq 0 ]]; then
tg_send_msg "${chat_id}" "🧹 No expired users found."
else
tg_send_msg "${chat_id}" "🧹 Removed ${removed} expired user(s):
${removed_list}"
fi
}
handle_backup() {
local chat_id="$1"
local callback_id="$2"
[[ -n "${callback_id}" ]] && tg_answer_callback "${callback_id}" "Creating backup..."
local backup_file="/tmp/iranux-backup-$(date +%Y%m%d%H%M%S).txt"
{
echo "# Iranux SSH Tunnel Backup - $(date)"
echo "# Domain: ${DOMAIN}"
echo ""
echo "# Users:"
while IFS=: read -r uname _ uid _; do
if [[ "$uid" -ge 1000 && "$uname" != "nobody" ]]; then
local u_exp u_limit
u_exp=$(chage -l "$uname" 2>/dev/null | grep "Account expires" | cut -d: -f2 | xargs || echo "Never")
u_limit=$(grep "^$uname soft maxlogins" /etc/security/limits.conf 2>/dev/null | awk '{print $4}' || echo "Unlimited")
echo " ${uname} | Expiry: ${u_exp} | Max Logins: ${u_limit}"
fi
done < /etc/passwd
} > "${backup_file}"
tg_send_document "${chat_id}" "${backup_file}" "💾 Iranux Backup - $(date +%Y-%m-%d)"
rm -f "${backup_file}"
}
handle_speedtest() {
local chat_id="$1"
local callback_id="$2"
[[ -n "${callback_id}" ]] && tg_answer_callback "${callback_id}" "Running speed test..."
tg_send_msg "${chat_id}" "⚡ Running speed test, please wait..."
local result
if command -v speedtest-cli >/dev/null 2>&1; then
if ! result=$(speedtest-cli --simple 2>&1 | head -5) || [[ -z "${result}" ]]; then
result="Speed test failed"
fi
elif command -v speedtest >/dev/null 2>&1; then
if ! result=$(speedtest 2>&1 | grep -E "Download|Upload|Ping" | head -5) || [[ -z "${result}" ]]; then
result="Speed test failed"
fi
else
local dl_speed
dl_speed=$(curl -s --max-time 15 -o /dev/null -w "%{speed_download}" \
"http://speedtest.tele2.net/10MB.zip" 2>/dev/null || echo "0")
local dl_mbps
dl_mbps=$(awk "BEGIN {printf \"%.2f\", ${dl_speed}/125000}" 2>/dev/null || echo "N/A")
result="Download: ~${dl_mbps} Mbps (approximate)"
fi
tg_send_msg "${chat_id}" "⚡ Speed Test Result:
${result}"
}
handle_help() {
local chat_id="$1"
local callback_id="$2"
[[ -n "${callback_id}" ]] && tg_answer_callback "${callback_id}" "Help"
tg_send_msg "${chat_id}" "❓ Iranux Bot Help
Use the menu buttons to manage SSH users.
Available actions:
• Add User — Create a new SSH user
• Del User — Delete an existing SSH user
• List Users — Show all SSH users
• Online Users — Show currently connected users
• User Info — Show details of a specific user
• Server Status — Show server status
• Change Pass — Change a user's password
• Change Limit — Modify max logins for a user
• Change Expiry — Modify expiry date for a user
• Remove Expired — Clean up expired accounts
• Backup — Create and send user backup
• Speed Test — Run server speed test
Send /menu to open the menu at any time."
}
# --- Conversation state machine ---
handle_conversation() {
local chat_id="$1"
local text="$2"
# Validate chat_id is numeric to prevent path traversal
[[ ! "${chat_id}" =~ ^-?[0-9]+$ ]] && return 1
local state_file="${STATE_DIR}/state.${chat_id}"
local data_file="${STATE_DIR}/data.${chat_id}"
[[ ! -f "${state_file}" ]] && return 1
local state
state=$(cat "${state_file}")
case "${state}" in
adduser_username)
echo "${text}" > "${data_file}"
echo "adduser_password" > "${state_file}"
tg_send_force_reply "${chat_id}" "🔑 Enter the password for user ${text}:"
;;
adduser_password)
local username
username=$(cat "${data_file}")
printf '%s\n%s\n' "${username}" "${text}" > "${data_file}"
echo "adduser_days" > "${state_file}"
tg_send_force_reply "${chat_id}" "📅 Enter expiry days (e.g. 30), or 0 for no expiry:"
;;
adduser_days)
local username password
username=$(sed -n '1p' "${data_file}")
password=$(sed -n '2p' "${data_file}")
printf '%s\n%s\n%s\n' "${username}" "${password}" "${text}" > "${data_file}"
echo "adduser_limit" > "${state_file}"
tg_send_force_reply "${chat_id}" "🔗 Enter max logins (e.g. 2), or 0 for unlimited:"
;;
adduser_limit)
local username password days limit
username=$(sed -n '1p' "${data_file}")
password=$(sed -n '2p' "${data_file}")
days=$(sed -n '3p' "${data_file}")
limit="${text}"
rm -f "${state_file}" "${data_file}"
[[ "${days}" == "0" ]] && days=""
[[ "${limit}" == "0" ]] && limit=""
local result
result=$(iranux /add "${username}" "${password}" "${days}" "${limit}" --json 2>/dev/null)
if echo "${result}" | jq -e '.status == "success"' > /dev/null 2>&1; then
local exp max_logins payload
exp=$(echo "${result}" | jq -r '.data.expiry // "Never"')
max_logins=$(echo "${result}" | jq -r '.data.max_logins // "Unlimited"')
payload=$(echo "${result}" | jq -r '.data.payload // ""')
tg_send_msg "${chat_id}" "✅ Iranux Config Created
--------------------------------
Protocol: SSH-TLS-Payload
Remarks: ${username}
SSH Host: ${DOMAIN}
SSH Port: 443
UDPGW Port: ${BADVPN_PORT}
SSH Username: ${username}
SSH Password: ${password}
SNI: ${DOMAIN}
Expiry: ${exp}
Max Logins: ${max_logins}
--------------------------------
👇 Payload (Copy Exact):
${payload}"
else
local errmsg
errmsg=$(echo "${result}" | jq -r '.message // "Unknown error"' 2>/dev/null || echo "Unknown error")
tg_send_msg "${chat_id}" "❌ Failed to create user: ${errmsg}"
fi
;;
deluser_username)
rm -f "${state_file}" "${data_file}"
local result
result=$(iranux /del "${text}" --json 2>/dev/null)
if echo "${result}" | jq -e '.status == "success"' > /dev/null 2>&1; then
tg_send_msg "${chat_id}" "✅ User ${text} deleted successfully."
else
local errmsg
errmsg=$(echo "${result}" | jq -r '.message // "Unknown error"' 2>/dev/null || echo "Unknown error")
tg_send_msg "${chat_id}" "❌ Failed to delete user: ${errmsg}"
fi
;;
userinfo_username)
rm -f "${state_file}" "${data_file}"
local result
result=$(iranux /info "${text}" --json 2>/dev/null)
if echo "${result}" | jq -e '.status == "success"' > /dev/null 2>&1; then
local exp max_logins
exp=$(echo "${result}" | jq -r '.data.expiry // "Never"')
max_logins=$(echo "${result}" | jq -r '.data.max_logins // "Unlimited"')
tg_send_msg "${chat_id}" "ℹ️ User Info: ${text}
Expiry: ${exp}
Max Logins: ${max_logins}"
else
local errmsg
errmsg=$(echo "${result}" | jq -r '.message // "Unknown error"' 2>/dev/null || echo "Unknown error")
tg_send_msg "${chat_id}" "❌ ${errmsg}"
fi
;;
changepass_username)
echo "${text}" > "${data_file}"
echo "changepass_password" > "${state_file}"
tg_send_force_reply "${chat_id}" "🔑 Enter the new password for user ${text}:"
;;
changepass_password)
local cp_user
cp_user=$(cat "${data_file}")
rm -f "${state_file}" "${data_file}"
if ! id "${cp_user}" &>/dev/null; then
tg_send_msg "${chat_id}" "❌ User ${cp_user} not found."
elif echo "${cp_user}:${text}" | chpasswd 2>/dev/null; then
tg_send_msg "${chat_id}" "✅ Password changed for ${cp_user}."
else
tg_send_msg "${chat_id}" "❌ Failed to change password for ${cp_user}."
fi
;;
changelimit_username)
echo "${text}" > "${data_file}"
echo "changelimit_value" > "${state_file}"
tg_send_force_reply "${chat_id}" "🔢 Enter the new max logins for user ${text} (or 0 for unlimited):"
;;
changelimit_value)
local cl_user
cl_user=$(cat "${data_file}")
rm -f "${state_file}" "${data_file}"
if ! id "${cl_user}" &>/dev/null; then
tg_send_msg "${chat_id}" "❌ User ${cl_user} not found."
else
sed -i "/^${cl_user}[[:space:]]\+soft[[:space:]]\+maxlogins/d" /etc/security/limits.conf
if [[ "${text}" != "0" && -n "${text}" ]]; then
echo "${cl_user} soft maxlogins ${text}" >> /etc/security/limits.conf
tg_send_msg "${chat_id}" "✅ Max logins for ${cl_user} set to ${text}."
else
tg_send_msg "${chat_id}" "✅ Max logins for ${cl_user} set to unlimited."
fi
fi
;;
changeexpiry_username)
echo "${text}" > "${data_file}"
echo "changeexpiry_days" > "${state_file}"
tg_send_force_reply "${chat_id}" "📅 Enter new expiry days for user ${text} (or 0 to remove expiry):"
;;
changeexpiry_days)
local ce_user
ce_user=$(cat "${data_file}")
rm -f "${state_file}" "${data_file}"
if ! id "${ce_user}" &>/dev/null; then
tg_send_msg "${chat_id}" "❌ User ${ce_user} not found."
else
if [[ "${text}" == "0" || -z "${text}" ]]; then
chage -E -1 "${ce_user}" 2>/dev/null
tg_send_msg "${chat_id}" "✅ Expiry removed for ${ce_user} (no expiry)."
elif [[ ! "${text}" =~ ^[0-9]+$ ]]; then
tg_send_msg "${chat_id}" "❌ Invalid input: please enter a number of days."
else
local new_exp
new_exp=$(date -d "+${text} days" +%Y-%m-%d 2>/dev/null)
if [[ -z "${new_exp}" ]]; then
tg_send_msg "${chat_id}" "❌ Invalid number of days: ${text}."
else
chage -E "${new_exp}" "${ce_user}" 2>/dev/null
tg_send_msg "${chat_id}" "✅ Expiry for ${ce_user} set to ${new_exp}."
fi
fi
fi
;;
*)
rm -f "${state_file}" "${data_file}"
return 1
;;
esac
return 0
}
# --- Process a single update ---
process_update() {
local update="$1"
# Handle callback_query
if echo "${update}" | jq -e '.callback_query' > /dev/null 2>&1; then
local cb_id cb_data from_id chat_id
cb_id=$(echo "${update}" | jq -r '.callback_query.id')
cb_data=$(echo "${update}" | jq -r '.callback_query.data')
from_id=$(echo "${update}" | jq -r '.callback_query.from.id')
chat_id=$(echo "${update}" | jq -r '.callback_query.message.chat.id')
if [[ "${from_id}" != "${ADMIN_ID}" ]]; then
tg_answer_callback "${cb_id}" "🚫 Access Denied"
return
fi
case "${cb_data}" in
_adduser) handle_adduser "${chat_id}" "${cb_id}" ;;
_deluser) handle_deluser "${chat_id}" "${cb_id}" ;;
_listusers) handle_listusers "${chat_id}" "${cb_id}" ;;
_online) handle_online "${chat_id}" "${cb_id}" ;;
_status) handle_status "${chat_id}" "${cb_id}" ;;
_userinfo) handle_userinfo "${chat_id}" "${cb_id}" ;;
_changepass) handle_changepass "${chat_id}" "${cb_id}" ;;
_changelimit) handle_changelimit "${chat_id}" "${cb_id}" ;;
_changeexpiry) handle_changeexpiry "${chat_id}" "${cb_id}" ;;
_removeexpired) handle_removeexpired "${chat_id}" "${cb_id}" ;;
_backup) handle_backup "${chat_id}" "${cb_id}" ;;
_speedtest) handle_speedtest "${chat_id}" "${cb_id}" ;;
_help) handle_help "${chat_id}" "${cb_id}" ;;
*) tg_answer_callback "${cb_id}" "" ;;
esac
return
fi
# Handle message
local chat_id from_id msg_text
chat_id=$(echo "${update}" | jq -r '.message.chat.id // empty')
from_id=$(echo "${update}" | jq -r '.message.from.id // empty')
msg_text=$(echo "${update}" | jq -r '.message.text // empty')
[[ -z "${chat_id}" || -z "${from_id}" ]] && return
if [[ "${from_id}" != "${ADMIN_ID}" ]]; then
tg_send_msg "${chat_id}" "🚫 ACCESS DENIED 🚫"
return
fi
[[ -z "${msg_text}" ]] && return
# Check for active conversation state first
handle_conversation "${chat_id}" "${msg_text}" && return
# Handle commands
case "${msg_text}" in
/start|/menu)
show_menu "${chat_id}"
;;
/help)
handle_help "${chat_id}" ""
;;
/add*)
read -r _ u_name u_pass u_days u_limit <<< "${msg_text}"
if [[ -z "${u_name}" || -z "${u_pass}" ]]; then
tg_send_msg "${chat_id}" "❌ Usage: /add username password [days] [limit]"
else
local result
result=$(iranux /add "${u_name}" "${u_pass}" "${u_days}" "${u_limit}" --json 2>/dev/null)
if echo "${result}" | jq -e '.status == "success"' > /dev/null 2>&1; then
local exp max_logins payload
exp=$(echo "${result}" | jq -r '.data.expiry // "Never"')
max_logins=$(echo "${result}" | jq -r '.data.max_logins // "Unlimited"')
payload=$(echo "${result}" | jq -r '.data.payload // ""')
tg_send_msg "${chat_id}" "✅ Iranux Config Created
--------------------------------
Protocol: SSH-TLS-Payload
Remarks: ${u_name}
SSH Host: ${DOMAIN}
SSH Port: 443
UDPGW Port: ${BADVPN_PORT}
SSH Username: ${u_name}
SSH Password: ${u_pass}
SNI: ${DOMAIN}
Expiry: ${exp}
Max Logins: ${max_logins}
--------------------------------
👇 Payload (Copy Exact):
${payload}"
else
local errmsg
errmsg=$(echo "${result}" | jq -r '.message // "Unknown error"' 2>/dev/null || echo "Unknown error")
tg_send_msg "${chat_id}" "❌ Failed to create user: ${errmsg}"
fi
fi
;;
/del*)
read -r _ u_del <<< "${msg_text}"
if [[ -z "${u_del}" ]]; then
tg_send_msg "${chat_id}" "❌ Usage: /del username"
else
local result
result=$(iranux /del "${u_del}" --json 2>/dev/null)
if echo "${result}" | jq -e '.status == "success"' > /dev/null 2>&1; then
tg_send_msg "${chat_id}" "✅ User ${u_del} deleted successfully."
else
local errmsg
errmsg=$(echo "${result}" | jq -r '.message // "Unknown error"' 2>/dev/null || echo "Unknown error")
tg_send_msg "${chat_id}" "❌ Failed to delete user: ${errmsg}"
fi
fi
;;
/list)
handle_listusers "${chat_id}" ""
;;
/status)
handle_status "${chat_id}" ""
;;
/info*)
read -r _ u_info <<< "${msg_text}"
if [[ -z "${u_info}" ]]; then
tg_send_msg "${chat_id}" "❌ Usage: /info username"
else
local result
result=$(iranux /info "${u_info}" --json 2>/dev/null)
if echo "${result}" | jq -e '.status == "success"' > /dev/null 2>&1; then
local exp max_logins
exp=$(echo "${result}" | jq -r '.data.expiry // "Never"')
max_logins=$(echo "${result}" | jq -r '.data.max_logins // "Unlimited"')
tg_send_msg "${chat_id}" "ℹ️ User Info: ${u_info}
Expiry: ${exp}
Max Logins: ${max_logins}"
else
local errmsg
errmsg=$(echo "${result}" | jq -r '.message // "Unknown error"' 2>/dev/null || echo "Unknown error")
tg_send_msg "${chat_id}" "❌ ${errmsg}"
fi
fi
;;
*)
show_menu "${chat_id}"
;;
esac
}
# --- Startup validation ---
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] Starting Iranux Telegram Bot..."
if [[ -z "${BOT_TOKEN}" || -z "${ADMIN_ID}" ]]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] BOT_TOKEN or ADMIN_ID not set in config.env"
exit 1
fi
test_resp=$(curl -s --max-time 10 "${API_BASE}/getMe")
if ! echo "${test_resp}" | jq -e '.ok == true' > /dev/null 2>&1; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] Invalid BOT_TOKEN or Telegram unreachable. Response: ${test_resp}"
exit 1
fi
bot_name=$(echo "${test_resp}" | jq -r '.result.username // "unknown"')
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] Bot validated: @${bot_name}"
tg_send_msg "${ADMIN_ID}" "🚀 Iranux Server Online!
Bot: @${bot_name}
------------------
Tap /menu to start."
# --- Main polling loop ---
last_update_id=0
err_count=0
while true; do
offset=$((last_update_id + 1))
response=$(curl -s --max-time 50 \
"${API_BASE}/getUpdates?offset=${offset}&limit=100&timeout=30")
curl_exit=$?
if [[ ${curl_exit} -ne 0 || -z "${response}" ]]; then
err_count=$((err_count + 1))
delay=$((2 ** err_count > 60 ? 60 : 2 ** err_count))
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [WARN] curl failed (attempt ${err_count}), retrying in ${delay}s..."
sleep "${delay}"
continue
fi
if ! echo "${response}" | jq -e '.ok == true' > /dev/null 2>&1; then
err_count=$((err_count + 1))
delay=$((2 ** err_count > 60 ? 60 : 2 ** err_count))
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [WARN] API error (attempt ${err_count}), retrying in ${delay}s..."
sleep "${delay}"
continue
fi
err_count=0
result_count=$(echo "${response}" | jq '.result | length')
[[ -z "${result_count}" || "${result_count}" == "0" ]] && continue
for i in $(seq 0 $((result_count - 1))); do
update=$(echo "${response}" | jq ".result[${i}]")
update_id=$(echo "${update}" | jq -r '.update_id')
[[ -z "${update_id}" ]] && continue
last_update_id=${update_id}
( process_update "${update}" ) &
done
wait
done
BOTEOF
chmod +x ${APP_DIR}/iranux-bot.sh
# ------------------------------------------------------------------------------
# PHASE 7: CLI MENU
# ------------------------------------------------------------------------------
cat << 'EOF' > /usr/local/bin/iranux
#!/bin/bash
CONFIG_FILE="/opt/iranux-tunnel/config.env"
if [ -f "$CONFIG_FILE" ]; then source "$CONFIG_FILE"; fi
RED='\033[0;31m'
GREEN='\033[0;32m'
CYAN='\033[0;36m'
NC='\033[0m'
# JSON output helpers
json_success() {
local cmd="$1"
local data="$2"
printf '{"status":"success","command":"%s","data":%s}\n' "$cmd" "$data"
}
json_error() {
local cmd="$1"
local code="$2"
local msg="$3"
printf '{"status":"error","command":"%s","code":%s,"message":"%s"}\n' "$cmd" "$code" "$msg"
exit "$code"
}
has_json() {
for arg in "$@"; do [[ "$arg" == "--json" ]] && return 0; done
return 1
}
# --- Non-interactive argument mode ---
if [[ $# -gt 0 ]]; then
case "$1" in
/add|add)
u_name="$2"
u_pass="$3"
u_days="$4"
u_limit="$5"
if [[ -z "$u_name" || -z "$u_pass" ]]; then
has_json "$@" && json_error "add" 5 "Invalid parameter: username and password required" || \
echo -e "${RED}Usage: iranux /add [days] [lim] Create user non-interactively"
echo -e " iranux /del