mirror of
https://oauth2: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@github.com/m1ngsama/TNT.git
synced 2026-06-26 05:44:38 +08:00
Tighten CLI option diagnostics
This commit is contained in:
parent
797ecbb992
commit
f0499c32f6
8 changed files with 165 additions and 31 deletions
3
Makefile
3
Makefile
|
|
@ -25,7 +25,7 @@ OBJECTS = $(SOURCES:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
|
||||||
DEPS = $(OBJECTS:.o=.d) $(CTL_OBJECTS:.o=.d)
|
DEPS = $(OBJECTS:.o=.d) $(CTL_OBJECTS:.o=.d)
|
||||||
TARGET = tnt
|
TARGET = tnt
|
||||||
CTL_TARGET = tntctl
|
CTL_TARGET = tntctl
|
||||||
CTL_OBJECTS = $(OBJ_DIR)/tntctl.o
|
CTL_OBJECTS = $(OBJ_DIR)/tntctl.o $(OBJ_DIR)/exec_catalog.o $(OBJ_DIR)/common.o
|
||||||
TARGETS = $(TARGET) $(CTL_TARGET)
|
TARGETS = $(TARGET) $(CTL_TARGET)
|
||||||
|
|
||||||
PREFIX ?= /usr/local
|
PREFIX ?= /usr/local
|
||||||
|
|
@ -122,6 +122,7 @@ unit-test:
|
||||||
|
|
||||||
script-test: all
|
script-test: all
|
||||||
@echo "Running script tests..."
|
@echo "Running script tests..."
|
||||||
|
@cd tests && ./test_cli_options.sh
|
||||||
@cd tests && ./test_logrotate.sh
|
@cd tests && ./test_logrotate.sh
|
||||||
@cd tests && ./test_message_log_tool.sh
|
@cd tests && ./test_message_log_tool.sh
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,9 +48,12 @@ PORT=3333 tnt # via env var
|
||||||
### Connecting
|
### Connecting
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
ssh -p 2222 chat.example.com
|
ssh -p 2222 localhost
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For a deployed server, replace `localhost` with your public host, for example
|
||||||
|
`chat.example.com`.
|
||||||
|
|
||||||
**Anonymous access by default**: Users can connect with ANY username/password (or empty password). No SSH keys required. Perfect for public chat servers.
|
**Anonymous access by default**: Users can connect with ANY username/password (or empty password). No SSH keys required. Perfect for public chat servers.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,13 @@
|
||||||
source release.
|
source release.
|
||||||
- Release documentation now creates the local tag before strict release checks,
|
- Release documentation now creates the local tag before strict release checks,
|
||||||
matching the strict gate's tag-at-HEAD requirement.
|
matching the strict gate's tag-at-HEAD requirement.
|
||||||
|
- Startup option parsing now reports missing values for `--bind`, `-p`,
|
||||||
|
`--idle-timeout`, and related flags with the localized
|
||||||
|
"option requires argument" diagnostic instead of treating the option as
|
||||||
|
unknown.
|
||||||
|
- `tntctl` now reuses the SSH exec command matcher for local command
|
||||||
|
validation, so `tntctl host --help` reaches the server-side exec help alias
|
||||||
|
instead of being rejected locally.
|
||||||
- Split UI-language parsing from localized text lookup: `src/i18n.c` now owns
|
- Split UI-language parsing from localized text lookup: `src/i18n.c` now owns
|
||||||
locale/code parsing, while `src/i18n_text.c` owns the table-driven text
|
locale/code parsing, while `src/i18n_text.c` owns the table-driven text
|
||||||
catalog with coverage checks for every message ID.
|
catalog with coverage checks for every message ID.
|
||||||
|
|
@ -121,6 +128,8 @@
|
||||||
reducing duplicate command knowledge in `src/exec.c`.
|
reducing duplicate command knowledge in `src/exec.c`.
|
||||||
- Replaced hard-coded `chat.m1ng.space` examples with `chat.example.com` so
|
- Replaced hard-coded `chat.m1ng.space` examples with `chat.example.com` so
|
||||||
public documentation does not imply a specific production host.
|
public documentation does not imply a specific production host.
|
||||||
|
- First-run connection examples now use `localhost`, keeping
|
||||||
|
`chat.example.com` for deployed public-host examples.
|
||||||
- Moved SSH exec usage text and argument-shape checks into `exec_catalog`, so
|
- Moved SSH exec usage text and argument-shape checks into `exec_catalog`, so
|
||||||
`src/exec.c` no longer duplicates `--json` and required-message validation.
|
`src/exec.c` no longer duplicates `--json` and required-message validation.
|
||||||
- Moved interactive command usage text and first-pass argument-shape checks
|
- Moved interactive command usage text and first-pass argument-shape checks
|
||||||
|
|
|
||||||
|
|
@ -37,9 +37,11 @@ tnt -p 2222 -d /var/lib/tnt
|
||||||
## Connect
|
## Connect
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
ssh -p 2222 chat.example.com
|
ssh -p 2222 localhost
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For a deployed server, replace `localhost` with your public host.
|
||||||
|
|
||||||
Default access rules:
|
Default access rules:
|
||||||
|
|
||||||
- Any SSH username is accepted.
|
- Any SSH username is accepted.
|
||||||
|
|
@ -199,9 +201,11 @@ tnt
|
||||||
### 连接
|
### 连接
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
ssh -p 2222 chat.example.com
|
ssh -p 2222 localhost
|
||||||
```
|
```
|
||||||
|
|
||||||
|
部署到公网后,将 `localhost` 替换为你的域名。
|
||||||
|
|
||||||
默认情况下,任意 SSH 用户名和空密码都可以连接。进入后 TNT 会询问显示名称。
|
默认情况下,任意 SSH 用户名和空密码都可以连接。进入后 TNT 会询问显示名称。
|
||||||
|
|
||||||
### 常用操作
|
### 常用操作
|
||||||
|
|
|
||||||
67
src/main.c
67
src/main.c
|
|
@ -75,6 +75,16 @@ static int set_numeric_env_option(const char *env_name, const char *opt_name,
|
||||||
return TNT_EXIT_OK;
|
return TNT_EXIT_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool require_option_arg(int argc, char **argv, int index,
|
||||||
|
ui_lang_t lang) {
|
||||||
|
if (index + 1 >= argc || argv[index + 1][0] == '\0') {
|
||||||
|
fprintf(stderr, cli_text_option_requires_arg_format(lang),
|
||||||
|
argv[index]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
int port = DEFAULT_PORT;
|
int port = DEFAULT_PORT;
|
||||||
ui_lang_t lang = i18n_default_ui_lang();
|
ui_lang_t lang = i18n_default_ui_lang();
|
||||||
|
|
@ -93,9 +103,11 @@ int main(int argc, char **argv) {
|
||||||
|
|
||||||
/* Parse command line arguments */
|
/* Parse command line arguments */
|
||||||
for (int i = 1; i < argc; i++) {
|
for (int i = 1; i < argc; i++) {
|
||||||
if ((strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--port") == 0) &&
|
if (strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--port") == 0) {
|
||||||
i + 1 < argc) {
|
|
||||||
int val;
|
int val;
|
||||||
|
if (!require_option_arg(argc, argv, i, lang)) {
|
||||||
|
return TNT_EXIT_USAGE;
|
||||||
|
}
|
||||||
if (!parse_int_arg(argv[i + 1], 1, 65535, &val)) {
|
if (!parse_int_arg(argv[i + 1], 1, 65535, &val)) {
|
||||||
fprintf(stderr, cli_text_invalid_port_format(lang),
|
fprintf(stderr, cli_text_invalid_port_format(lang),
|
||||||
argv[i + 1]);
|
argv[i + 1]);
|
||||||
|
|
@ -103,18 +115,19 @@ int main(int argc, char **argv) {
|
||||||
}
|
}
|
||||||
port = val;
|
port = val;
|
||||||
i++;
|
i++;
|
||||||
} else if ((strcmp(argv[i], "-d") == 0 ||
|
} else if (strcmp(argv[i], "-d") == 0 ||
|
||||||
strcmp(argv[i], "--state-dir") == 0) && i + 1 < argc) {
|
strcmp(argv[i], "--state-dir") == 0) {
|
||||||
if (argv[i + 1][0] == '\0') {
|
if (!require_option_arg(argc, argv, i, lang)) {
|
||||||
fprintf(stderr, cli_text_invalid_value_format(lang),
|
|
||||||
argv[i], argv[i + 1]);
|
|
||||||
return TNT_EXIT_USAGE;
|
return TNT_EXIT_USAGE;
|
||||||
}
|
}
|
||||||
if (set_env_option("TNT_STATE_DIR", argv[i + 1]) != 0) {
|
if (set_env_option("TNT_STATE_DIR", argv[i + 1]) != 0) {
|
||||||
return TNT_EXIT_ERROR;
|
return TNT_EXIT_ERROR;
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
} else if (strcmp(argv[i], "--bind") == 0 && i + 1 < argc) {
|
} else if (strcmp(argv[i], "--bind") == 0) {
|
||||||
|
if (!require_option_arg(argc, argv, i, lang)) {
|
||||||
|
return TNT_EXIT_USAGE;
|
||||||
|
}
|
||||||
if (!is_config_token(argv[i + 1])) {
|
if (!is_config_token(argv[i + 1])) {
|
||||||
fprintf(stderr, cli_text_invalid_value_format(lang),
|
fprintf(stderr, cli_text_invalid_value_format(lang),
|
||||||
argv[i], argv[i + 1]);
|
argv[i], argv[i + 1]);
|
||||||
|
|
@ -124,7 +137,10 @@ int main(int argc, char **argv) {
|
||||||
return TNT_EXIT_ERROR;
|
return TNT_EXIT_ERROR;
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
} else if (strcmp(argv[i], "--public-host") == 0 && i + 1 < argc) {
|
} else if (strcmp(argv[i], "--public-host") == 0) {
|
||||||
|
if (!require_option_arg(argc, argv, i, lang)) {
|
||||||
|
return TNT_EXIT_USAGE;
|
||||||
|
}
|
||||||
if (!is_config_token(argv[i + 1])) {
|
if (!is_config_token(argv[i + 1])) {
|
||||||
fprintf(stderr, cli_text_invalid_value_format(lang),
|
fprintf(stderr, cli_text_invalid_value_format(lang),
|
||||||
argv[i], argv[i + 1]);
|
argv[i], argv[i + 1]);
|
||||||
|
|
@ -134,8 +150,10 @@ int main(int argc, char **argv) {
|
||||||
return TNT_EXIT_ERROR;
|
return TNT_EXIT_ERROR;
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
} else if (strcmp(argv[i], "--max-connections") == 0 &&
|
} else if (strcmp(argv[i], "--max-connections") == 0) {
|
||||||
i + 1 < argc) {
|
if (!require_option_arg(argc, argv, i, lang)) {
|
||||||
|
return TNT_EXIT_USAGE;
|
||||||
|
}
|
||||||
int rc = set_numeric_env_option("TNT_MAX_CONNECTIONS", argv[i],
|
int rc = set_numeric_env_option("TNT_MAX_CONNECTIONS", argv[i],
|
||||||
argv[i + 1], 1,
|
argv[i + 1], 1,
|
||||||
MAX_CONFIGURED_CLIENTS, lang);
|
MAX_CONFIGURED_CLIENTS, lang);
|
||||||
|
|
@ -143,8 +161,10 @@ int main(int argc, char **argv) {
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
} else if (strcmp(argv[i], "--max-conn-per-ip") == 0 &&
|
} else if (strcmp(argv[i], "--max-conn-per-ip") == 0) {
|
||||||
i + 1 < argc) {
|
if (!require_option_arg(argc, argv, i, lang)) {
|
||||||
|
return TNT_EXIT_USAGE;
|
||||||
|
}
|
||||||
int rc = set_numeric_env_option("TNT_MAX_CONN_PER_IP", argv[i],
|
int rc = set_numeric_env_option("TNT_MAX_CONN_PER_IP", argv[i],
|
||||||
argv[i + 1], 1,
|
argv[i + 1], 1,
|
||||||
MAX_CONFIGURED_CLIENTS, lang);
|
MAX_CONFIGURED_CLIENTS, lang);
|
||||||
|
|
@ -152,8 +172,10 @@ int main(int argc, char **argv) {
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
} else if (strcmp(argv[i], "--max-conn-rate-per-ip") == 0 &&
|
} else if (strcmp(argv[i], "--max-conn-rate-per-ip") == 0) {
|
||||||
i + 1 < argc) {
|
if (!require_option_arg(argc, argv, i, lang)) {
|
||||||
|
return TNT_EXIT_USAGE;
|
||||||
|
}
|
||||||
int rc = set_numeric_env_option("TNT_MAX_CONN_RATE_PER_IP",
|
int rc = set_numeric_env_option("TNT_MAX_CONN_RATE_PER_IP",
|
||||||
argv[i], argv[i + 1], 1,
|
argv[i], argv[i + 1], 1,
|
||||||
MAX_CONFIGURED_CLIENTS, lang);
|
MAX_CONFIGURED_CLIENTS, lang);
|
||||||
|
|
@ -161,21 +183,30 @@ int main(int argc, char **argv) {
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
} else if (strcmp(argv[i], "--rate-limit") == 0 && i + 1 < argc) {
|
} else if (strcmp(argv[i], "--rate-limit") == 0) {
|
||||||
|
if (!require_option_arg(argc, argv, i, lang)) {
|
||||||
|
return TNT_EXIT_USAGE;
|
||||||
|
}
|
||||||
int rc = set_numeric_env_option("TNT_RATE_LIMIT", argv[i],
|
int rc = set_numeric_env_option("TNT_RATE_LIMIT", argv[i],
|
||||||
argv[i + 1], 0, 1, lang);
|
argv[i + 1], 0, 1, lang);
|
||||||
if (rc != TNT_EXIT_OK) {
|
if (rc != TNT_EXIT_OK) {
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
} else if (strcmp(argv[i], "--idle-timeout") == 0 && i + 1 < argc) {
|
} else if (strcmp(argv[i], "--idle-timeout") == 0) {
|
||||||
|
if (!require_option_arg(argc, argv, i, lang)) {
|
||||||
|
return TNT_EXIT_USAGE;
|
||||||
|
}
|
||||||
int rc = set_numeric_env_option("TNT_IDLE_TIMEOUT", argv[i],
|
int rc = set_numeric_env_option("TNT_IDLE_TIMEOUT", argv[i],
|
||||||
argv[i + 1], 0, 86400, lang);
|
argv[i + 1], 0, 86400, lang);
|
||||||
if (rc != TNT_EXIT_OK) {
|
if (rc != TNT_EXIT_OK) {
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
} else if (strcmp(argv[i], "--ssh-log-level") == 0 && i + 1 < argc) {
|
} else if (strcmp(argv[i], "--ssh-log-level") == 0) {
|
||||||
|
if (!require_option_arg(argc, argv, i, lang)) {
|
||||||
|
return TNT_EXIT_USAGE;
|
||||||
|
}
|
||||||
int rc = set_numeric_env_option("TNT_SSH_LOG_LEVEL", argv[i],
|
int rc = set_numeric_env_option("TNT_SSH_LOG_LEVEL", argv[i],
|
||||||
argv[i + 1], 0, 4, lang);
|
argv[i + 1], 0, 4, lang);
|
||||||
if (rc != TNT_EXIT_OK) {
|
if (rc != TNT_EXIT_OK) {
|
||||||
|
|
|
||||||
11
src/tntctl.c
11
src/tntctl.c
|
|
@ -1,4 +1,5 @@
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
#include "exec_catalog.h"
|
||||||
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
@ -73,15 +74,7 @@ static bool is_host_key_checking_mode(const char *value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool is_known_exec_command(const char *command) {
|
static bool is_known_exec_command(const char *command) {
|
||||||
return command &&
|
return exec_catalog_match(command, NULL, NULL);
|
||||||
(strcmp(command, "health") == 0 ||
|
|
||||||
strcmp(command, "stats") == 0 ||
|
|
||||||
strcmp(command, "users") == 0 ||
|
|
||||||
strcmp(command, "tail") == 0 ||
|
|
||||||
strcmp(command, "dump") == 0 ||
|
|
||||||
strcmp(command, "post") == 0 ||
|
|
||||||
strcmp(command, "help") == 0 ||
|
|
||||||
strcmp(command, "exit") == 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int build_remote_command(char *buffer, size_t buf_size, int argc,
|
static int build_remote_command(char *buffer, size_t buf_size, int argc,
|
||||||
|
|
|
||||||
82
tests/test_cli_options.sh
Executable file
82
tests/test_cli_options.sh
Executable file
|
|
@ -0,0 +1,82 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# CLI option parsing regression tests.
|
||||||
|
|
||||||
|
BIN="../tnt"
|
||||||
|
PASS=0
|
||||||
|
FAIL=0
|
||||||
|
|
||||||
|
pass() {
|
||||||
|
echo "✓ $1"
|
||||||
|
PASS=$((PASS + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "✗ $1"
|
||||||
|
if [ -n "$2" ]; then
|
||||||
|
printf '%s\n' "$2"
|
||||||
|
fi
|
||||||
|
FAIL=$((FAIL + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ ! -f "$BIN" ]; then
|
||||||
|
echo "Error: Binary $BIN not found. Run make first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
expect_missing_arg() {
|
||||||
|
opt="$1"
|
||||||
|
output=$("$BIN" "$opt" 2>&1)
|
||||||
|
status=$?
|
||||||
|
|
||||||
|
if [ "$status" -eq 64 ] &&
|
||||||
|
printf '%s\n' "$output" | grep -q "Option requires argument: $opt"; then
|
||||||
|
pass "$opt reports missing argument"
|
||||||
|
else
|
||||||
|
fail "$opt missing argument diagnostic unexpected" "$output"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "=== TNT CLI Option Tests ==="
|
||||||
|
|
||||||
|
for opt in \
|
||||||
|
-p \
|
||||||
|
--port \
|
||||||
|
-d \
|
||||||
|
--state-dir \
|
||||||
|
--bind \
|
||||||
|
--public-host \
|
||||||
|
--max-connections \
|
||||||
|
--max-conn-per-ip \
|
||||||
|
--max-conn-rate-per-ip \
|
||||||
|
--rate-limit \
|
||||||
|
--idle-timeout \
|
||||||
|
--ssh-log-level \
|
||||||
|
--log-check \
|
||||||
|
--log-recover
|
||||||
|
do
|
||||||
|
expect_missing_arg "$opt"
|
||||||
|
done
|
||||||
|
|
||||||
|
ZH_OUTPUT=$(TNT_LANG=zh "$BIN" --bind 2>&1)
|
||||||
|
ZH_STATUS=$?
|
||||||
|
if [ "$ZH_STATUS" -eq 64 ] &&
|
||||||
|
printf '%s\n' "$ZH_OUTPUT" | grep -q '选项需要参数: --bind'; then
|
||||||
|
pass "missing argument diagnostic follows TNT_LANG"
|
||||||
|
else
|
||||||
|
fail "localized missing argument diagnostic unexpected" "$ZH_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
BAD_PORT_OUTPUT=$("$BIN" --port abc 2>&1)
|
||||||
|
BAD_PORT_STATUS=$?
|
||||||
|
if [ "$BAD_PORT_STATUS" -eq 64 ] &&
|
||||||
|
printf '%s\n' "$BAD_PORT_OUTPUT" | grep -q 'Invalid port: abc'; then
|
||||||
|
pass "invalid port still reports invalid value"
|
||||||
|
else
|
||||||
|
fail "invalid port diagnostic unexpected" "$BAD_PORT_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "PASSED: $PASS"
|
||||||
|
echo "FAILED: $FAIL"
|
||||||
|
[ "$FAIL" -eq 0 ] && echo "All tests passed" || echo "Some tests failed"
|
||||||
|
exit "$FAIL"
|
||||||
|
|
@ -119,6 +119,17 @@ else
|
||||||
FAIL=$((FAIL + 1))
|
FAIL=$((FAIL + 1))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
run_ok "remote help alias is accepted" "$BIN" example.com --help
|
||||||
|
grep -q '^--help$' "$SSH_LOG"
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "✓ --help after host is forwarded as exec help"
|
||||||
|
PASS=$((PASS + 1))
|
||||||
|
else
|
||||||
|
echo "✗ remote --help command unexpected"
|
||||||
|
cat "$SSH_LOG"
|
||||||
|
FAIL=$((FAIL + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
PATH="$FAKE_BIN:$PATH" TNTCTL_SSH_LOG="$SSH_LOG" "$BIN" example.com users --xml >/dev/null 2>&1
|
PATH="$FAKE_BIN:$PATH" TNTCTL_SSH_LOG="$SSH_LOG" "$BIN" example.com users --xml >/dev/null 2>&1
|
||||||
REMOTE_STATUS=$?
|
REMOTE_STATUS=$?
|
||||||
if [ "$REMOTE_STATUS" -eq 64 ]; then
|
if [ "$REMOTE_STATUS" -eq 64 ]; then
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue