更新
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
// One cache entry for the public homepage list; request arguments never enter its key.
|
||||
function txiaw_read_home_news_cache($file)
|
||||
{
|
||||
$raw = @file_get_contents($file);
|
||||
if ($raw === false) {
|
||||
return false;
|
||||
}
|
||||
$entry = json_decode($raw, true);
|
||||
if (!is_array($entry) || !isset($entry['created'], $entry['items']) ||
|
||||
!is_numeric($entry['created']) || !is_array($entry['items']) ||
|
||||
$entry['created'] > time() + 5) {
|
||||
return false;
|
||||
}
|
||||
return $entry;
|
||||
}
|
||||
|
||||
function txiaw_home_news_cached($cacheDirectory = null, $loader = null)
|
||||
{
|
||||
if ($cacheDirectory === null) {
|
||||
$cacheDirectory = dirname(__DIR__) . '/Runtime/TxiawHomeNews';
|
||||
}
|
||||
if ($loader === null) {
|
||||
$loader = function () {
|
||||
return gxl_mysql_news('field:news_id,news_cid,news_name;limit:100;order:news_addtime desc');
|
||||
};
|
||||
}
|
||||
$file = $cacheDirectory . '/news-list-v1.json';
|
||||
$entry = txiaw_read_home_news_cache($file);
|
||||
if ($entry !== false && time() - $entry['created'] < 60) {
|
||||
return $entry['items'];
|
||||
}
|
||||
if (!is_dir($cacheDirectory) && !@mkdir($cacheDirectory, 0750, true) && !is_dir($cacheDirectory)) {
|
||||
return call_user_func($loader);
|
||||
}
|
||||
$lock = @fopen($cacheDirectory . '/news-list-v1.lock', 'c');
|
||||
if ($lock === false) {
|
||||
return call_user_func($loader);
|
||||
}
|
||||
if (!flock($lock, LOCK_EX | LOCK_NB)) {
|
||||
fclose($lock);
|
||||
// Other requests can use a recently expired value while one request refreshes it.
|
||||
if ($entry !== false && time() - $entry['created'] < 300) {
|
||||
return $entry['items'];
|
||||
}
|
||||
// A cold cache gets a bounded wait; never keep an entire PHP pool waiting on a lock.
|
||||
for ($attempt = 0; $attempt < 20; $attempt++) {
|
||||
usleep(10000);
|
||||
$entry = txiaw_read_home_news_cache($file);
|
||||
if ($entry !== false && time() - $entry['created'] < 60) {
|
||||
return $entry['items'];
|
||||
}
|
||||
}
|
||||
// Preserve page availability if the cache filesystem or refresher fails.
|
||||
// The indexed query and Nginx limits still bound database work in this fallback.
|
||||
return call_user_func($loader);
|
||||
}
|
||||
try {
|
||||
// Another request may have refreshed between our first read and lock acquisition.
|
||||
$latest = txiaw_read_home_news_cache($file);
|
||||
if ($latest !== false && time() - $latest['created'] < 60) {
|
||||
return $latest['items'];
|
||||
}
|
||||
try {
|
||||
$items = call_user_func($loader);
|
||||
} catch (Exception $exception) {
|
||||
if ($entry !== false && time() - $entry['created'] < 300) {
|
||||
return $entry['items'];
|
||||
}
|
||||
throw $exception;
|
||||
}
|
||||
if (is_array($items)) {
|
||||
$encoded = json_encode(array('created' => time(), 'items' => $items));
|
||||
$temporary = $encoded === false ? false : @tempnam($cacheDirectory, '.news-');
|
||||
if ($temporary !== false) {
|
||||
@chmod($temporary, 0640);
|
||||
$written = @file_put_contents($temporary, $encoded);
|
||||
if ($written !== strlen($encoded) || !@rename($temporary, $file)) {
|
||||
@unlink($temporary);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $items;
|
||||
} finally {
|
||||
flock($lock, LOCK_UN);
|
||||
fclose($lock);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
// Reject the observed homepage flood before loading ThinkPHP or opening MySQL.
|
||||
// Nginx also enforces homepage rate limits before a PHP worker is allocated.
|
||||
function txiaw_should_block_home_request($server, $query)
|
||||
{
|
||||
$method = isset($server['REQUEST_METHOD']) ? strtoupper($server['REQUEST_METHOD']) : '';
|
||||
if ($method !== 'GET' && $method !== 'HEAD') {
|
||||
return false;
|
||||
}
|
||||
$requestUri = isset($server['REQUEST_URI']) ? $server['REQUEST_URI'] : '';
|
||||
$path = explode('?', $requestUri, 2);
|
||||
$path = rawurldecode($path[0]);
|
||||
if ($path !== '/' && $path !== '/index.php') {
|
||||
return false;
|
||||
}
|
||||
$userAgent = isset($server['HTTP_USER_AGENT']) ? trim($server['HTTP_USER_AGENT']) : '';
|
||||
return $userAgent === '' && array_key_exists('r', $query);
|
||||
}
|
||||
|
||||
if (PHP_SAPI !== 'cli' && txiaw_should_block_home_request($_SERVER, $_GET)) {
|
||||
http_response_code(429);
|
||||
header('Retry-After: 60');
|
||||
header('Cache-Control: no-store');
|
||||
header('Content-Type: text/plain; charset=UTF-8');
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'HEAD') {
|
||||
echo "Too many requests. Please retry later.\n";
|
||||
}
|
||||
exit;
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
python3 -c 'import sys,gzip,base64;exec(gzip.decompress(base64.b64decode(sys.argv[1])))' H4sIABlLlWoC/5VWYW/bNhD9rl/BpUkpzZLsdF1XOBCKAc3WAu1StMmHwXUFWqJsLhLJklTtoE5/++4oxba6ZMMCOJap4+Pj8d07ikYr48hfVsmYKBsT65iD/+1CG1VwCyOON7oSNYcn0fBgwSzP6Hi9Xo8XrLhu9dhtBFsnzY39XCeV2CRPJk+eTZ7/dEqDUhheOGVu+gmWmy/cjDWTvB5/WSnrxnIp5IYGVjie7cJHPjz1wGmhGvjIigY+dgjlh8Z2IeQOybFGZ0g1tc5U+BDSkz9PmpMyOXl18vbkA42CjnmGe4G1FrxShids0VqewFISWAglk6JWMEBHHjJQNm2ugWLYTY4n6pfJJAqsag0kKvtKuzysVMMTSJ7rQVbO6VTIgk4JfUTeKFbykghJcJzAYo5vIOFK1jfE8IobLgsIWNwQt+LEQ5IvwriW1QQTln6Uj8iFEbBZGDH8c8uty1sjiLAE+JfEKhheG+Ecl4QZJ4qak6v3byz84EQqR5CiZkuAN6yqRAGYDdPk+BDt2C+dY2iumVuRrx8lgb+SV6ytHZmcdb+/fRqHQpZ8M0vneqWjF+HsxXx7HJFTCLjtgZlZ2gGiYbJUTQ7jD+KGn7aPIxNm28ffoR3teDbcrVS5/Z7q9v6VtseY8hxyZHLYvHRHA0pFkS9qVVw/ROjo26fw9/PL7avzX19Gs+38tP/MZlOrWcGn8/mPx0d3RGlMHtBDJ9y9Ii7w5PGsUePCNHCCUiVXv5KOeWL2x1XVSpV4zl6YIBKvW/Lu1Tuvij8U0dwkr98RZQhbLg1fMsf30/u0JQZHa9EIh1hcskUNYCuQHsCIioT3pSW6y4vhrjWSPH36tN/pbaDqMvuqp0pzGeqYmgWNUsNZGUYECBKNcp9hhc9Hs8Map6OND9hgQF9H89vAz4lL5hiOA3gKUxsbRtMACawFqNGv1RWiB4LixKNPsaIlg5LXUUzXQIQwS6pplWJB8BBBI48CE4pVo8r/ApmoZ1jloAaiWxd2vHom1mUwBR0TIs+qMnboPL1dgllYfA41lLUA2+rsLKExpGBvdR0bZ246yP0GbVqVfpeA+9BOzqq0qlu7CqMzjLc3sghhCFaXKoyiHeJus0AwRr7ph/z124uX5yEYChaSKnnkMYqVWss+DN+0ouyflqL0EYbrGuTuY3S3RIVeVB/sAER0l0u+EdZZjI6mMNbKWshr/9Pn1LQyRHfoM9qLa99+gBAvrnPVOsw+RgKdkhuTHcR8uHx5cXUZo9VDYHb6c5SWvIA9hbR1VfKcgiY71mD9vsyWLRQBVGMG6uqU+d2MKPBmAVVatyX0u/6B0NGhgP/d8c+gGfli38P45PwPqL1ZABhk9ZCUt3IokMGGpsPtHcaPKBTraPAeIYcUsWcKDYV7L7g/o4aZa266vfSTsVjIoGOT3RMQ7yUxwIJXrXRhBxb9kJ1ODROWk/cwCud4bowyIb2SfKMhF9jWDpYaIJ0BV4KaB19cMbnkJe1kOUzFcPU7FXfrx91Xl6FhQuLTKNhVJzoTEtiZU29aO4NCkQ4dbheeQmM/FFjHESeg/uIhvXtjHZg3LzMsmZm/7MQ0cXTevRyMWq/4Gu4a+JpvCq4dOfdfu2N82GUPfO6f0PsF8bgCA/ceNJTW0inddbDcX6Xyrk3l6GdQl7kXo+FWK2m5Z9i1cexHue9HNu+7EZ3+xmrLY9q5M532dy7qaeSYiVxdw5K2LdACqramuI0uRbcB3mfTsgX3NXHfKvxN7/4rXorhQGhNoxjvMtJlT6JAGwEC3SHZ0ESxd9vs0rQ8Cv4GWZFh0TsLAAA=
|
||||
@@ -0,0 +1 @@
|
||||
{"status": "confirmed_abuse_closed_without_http_response", "request_rate_limits_enabled": false, "backup": "/www/backup/txiaw-mysql-fix-20260831/before-abuse-connection-close-20260831-173658", "nginx_test_ok": true}
|
||||
@@ -0,0 +1,24 @@
|
||||
[
|
||||
{
|
||||
"route": "normal",
|
||||
"port": 80,
|
||||
"status": 200,
|
||||
"bytes_read": 16848,
|
||||
"seconds": 4.66,
|
||||
"title": "PC\u6e38\u620f\u4e0b\u8f7d\uff0c\u5b89\u5353\u6e38\u620f\u4e0b\u8f7d\uff0c\u624b\u673a\u6e38\u620f\u4e0b\u8f7d\uff0c\u624b\u673a\u8f6f\u4ef6\u4e0b\u8f7d\uff0c\u5b89\u5353\u8f6f\u4ef6\u4e0b\u8f7d\uff0c\u9e3f\u8499\u8f6f\u4ef6\u4e0b\u8f7d\uff0c\u5b83\u4e0b\u7f51"
|
||||
},
|
||||
{
|
||||
"route": "normal",
|
||||
"port": 8686,
|
||||
"error": "_ssl.c:989: The handshake operation timed out",
|
||||
"seconds": 10.02
|
||||
},
|
||||
{
|
||||
"route": "proxy",
|
||||
"port": 8686,
|
||||
"status": 200,
|
||||
"bytes_read": 25502,
|
||||
"seconds": 7.09,
|
||||
"title": "\u5b9d\u5854Linux\u9762\u677f"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
{"time": "2026-08-31 17:37:44", "nginx_test_ok": true, "abuse_drop_rule_loaded": true, "txiaw_home_frequency_limits_loaded": false, "uptime": "17:37:44 up 1:03, 0 users, load average: 4.00, 3.62, 3.07", "panel_listener": "State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess\nLISTEN 0 100 0.0.0.0:8686 0.0.0.0:* users:((\"BT-Panel\",pid=1272,fd=3))", "panel_processes": "PID STAT %CPU RSS NLWP WCHAN COMMAND\n 1272 S 0.0 63788 1 - BT-Panel\n 1349 Sl 0.0 13084 6 - BT-Task", "tcp_by_service_and_state": {"8686 LISTEN": 1, "80 LISTEN": 1, "22 LISTEN": 2, "443 LISTEN": 1, "80 ESTAB": 1539, "80 TIME-WAIT": 4689, "80 FIN-WAIT-1": 2383, "80 SYN-RECV": 503, "80 LAST-ACK": 134, "80 FIN-WAIT-2": 161, "443 TIME-WAIT": 25, "80 CLOSING": 110, "443 ESTAB": 28, "443 FIN-WAIT-1": 2, "443 LAST-ACK": 2, "8686 ESTAB": 1, "22 ESTAB": 2, "3306 LISTEN": 1, "3306 ESTAB": 7, "3306 TIME-WAIT": 8}, "network_Mbps": {"eth0": {"rx": 6.99, "tx": 4.25}}, "cpu_idle_pct": 66.0, "home": {"status": 200, "bytes": 16848, "seconds": 1.09}, "article": {"status": 200, "bytes": 31198, "seconds": 2.1}, "observed_abuse": {"result": "RemoteDisconnected", "detail": "Remote end closed connection without response", "seconds": 3.05}, "other_site": {"status": 200, "bytes": 14578, "seconds": 0.03}, "panel": {"phase": "http", "first_line": "HTTP/1.1 200 OK", "seconds": 0.14, "bytes_received": 342}, "recent_access_statuses": {"444": 999, "200": 1}, "recent_access_time_range": ["31/Aug/2026:17:37:52 +0800", "31/Aug/2026:17:37:53 +0800"], "logged_response_body_bytes": 3699}
|
||||
@@ -0,0 +1,86 @@
|
||||
import gzip
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
BASE = '/www/backup/txiaw-mysql-fix-20260831'
|
||||
config = open('/www/wwwroot/www.txiaw.com/Runtime/Conf/config.php', encoding='utf-8').read()
|
||||
cfg = {m.group(1).lower(): m.group(3) for m in re.finditer(r'''['"](db_[a-z_]+)['"]\s*=>\s*(['"])(.*?)\2''', config, re.I)}
|
||||
assert cfg['db_name'] == 'xiaxia'
|
||||
env = dict(os.environ, MYSQL_PWD=cfg.get('db_pwd', cfg.get('db_password')))
|
||||
auth = ['--no-defaults', '-u' + cfg['db_user'], '-h' + cfg['db_host'], '--connect-timeout=5']
|
||||
mysql = ['/www/server/mysql/bin/mysql'] + auth + ['-B']
|
||||
|
||||
def sql(query, timeout=20):
|
||||
p = subprocess.run(mysql + ['-e', query], env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, timeout=timeout)
|
||||
if p.returncode:
|
||||
raise RuntimeError(p.stderr[:1500])
|
||||
return p.stdout
|
||||
|
||||
schema = sql('SHOW CREATE TABLE xiaxia.gxl_news; SHOW INDEX FROM xiaxia.gxl_news;')
|
||||
schema_path = BASE + '/gxl_news-schema-before.txt'
|
||||
if not os.path.exists(schema_path):
|
||||
with open(schema_path, 'x') as f:
|
||||
f.write(schema)
|
||||
os.chmod(schema_path, 0o600)
|
||||
|
||||
indexes = {}
|
||||
for line in sql('SHOW INDEX FROM xiaxia.gxl_news;').splitlines()[1:]:
|
||||
cols = line.split('\t')
|
||||
indexes.setdefault(cols[2], []).append((int(cols[3]), cols[4]))
|
||||
compatible = [name for name, values in indexes.items() if [col for _, col in sorted(values)][:2] == ['news_status', 'news_addtime']]
|
||||
if compatible:
|
||||
print('SUITABLE_INDEX_ALREADY_EXISTS', compatible, flush=True)
|
||||
else:
|
||||
if 'idx_news_status_addtime' in indexes:
|
||||
raise RuntimeError('Index name exists with different columns; refusing replacement')
|
||||
backup = BASE + '/gxl_news-before.sql.gz'
|
||||
if not os.path.exists(backup):
|
||||
partial = backup + '.partial'
|
||||
if os.path.exists(partial):
|
||||
with gzip.open(partial, 'rb') as previous:
|
||||
if previous.read(1):
|
||||
raise RuntimeError('Nonempty partial backup exists; inspect before retry')
|
||||
os.rename(partial, partial + '.empty-failed-' + str(int(time.time())))
|
||||
print('BACKING_UP_GXL_NEWS_WITH_CONSISTENT_SNAPSHOT', flush=True)
|
||||
dump_auth = [a for a in auth if not a.startswith('--connect-timeout=')]
|
||||
dump_args = ['/www/server/mysql/bin/mysqldump'] + dump_auth + ['--single-transaction', '--quick', '--skip-lock-tables', '--skip-add-locks', '--hex-blob', '--set-gtid-purged=OFF', 'xiaxia', 'gxl_news']
|
||||
with open(BASE + '/dump-stderr.txt', 'wb') as err:
|
||||
p = subprocess.Popen(dump_args, env=env, stdout=subprocess.PIPE, stderr=err)
|
||||
with open(partial, 'xb') as raw:
|
||||
os.chmod(partial, 0o600)
|
||||
with gzip.GzipFile(fileobj=raw, mode='wb', compresslevel=1) as target:
|
||||
shutil.copyfileobj(p.stdout, target, 1024 * 1024)
|
||||
p.stdout.close()
|
||||
if p.wait(timeout=90) != 0:
|
||||
raise RuntimeError('Backup failed; see private dump-stderr.txt. No ALTER performed.')
|
||||
with gzip.open(partial, 'rb') as check:
|
||||
while check.read(1024 * 1024):
|
||||
pass
|
||||
os.replace(partial, backup)
|
||||
print('BACKUP_VERIFIED', backup, os.path.getsize(backup), flush=True)
|
||||
print('ADDING_SECONDARY_INDEX_INPLACE_LOCK_NONE', flush=True)
|
||||
started = time.monotonic()
|
||||
sql('SET SESSION lock_wait_timeout=5; ALTER TABLE xiaxia.gxl_news ADD INDEX idx_news_status_addtime (news_status, news_addtime), ALGORITHM=INPLACE, LOCK=NONE;', timeout=90)
|
||||
print('INDEX_ADDED_SECONDS', round(time.monotonic() - started, 3), flush=True)
|
||||
|
||||
query = 'SELECT SQL_NO_CACHE news_id,news_cid,news_name FROM xiaxia.gxl_news WHERE news_status=1 ORDER BY news_addtime DESC LIMIT 100'
|
||||
plan = sql('EXPLAIN ' + query + ';')
|
||||
print('EXPLAIN_AFTER\n' + plan, flush=True)
|
||||
if 'Using filesort' in plan:
|
||||
raise RuntimeError('The optimizer still sorts; inspect before claiming success')
|
||||
samples = []
|
||||
for _ in range(3):
|
||||
started = time.monotonic()
|
||||
result = sql(query + ';')
|
||||
samples.append({'wall_seconds_including_client': round(time.monotonic() - started, 4), 'rows': len(result.splitlines()) - 1})
|
||||
assert all(s['rows'] == 100 for s in samples)
|
||||
metrics = sql(query + "; SHOW SESSION STATUS WHERE Variable_name IN ('Sort_rows','Sort_scan','Sort_range','Handler_read_key','Handler_read_next','Handler_read_prev');")
|
||||
metrics = metrics[metrics.rfind('Variable_name\tValue'):]
|
||||
print('QUERY_SAMPLES', json.dumps(samples), flush=True)
|
||||
print('SESSION_COUNTERS_AFTER_ONE_QUERY\n' + metrics, flush=True)
|
||||
with open(BASE + '/index-result.json', 'w') as f:
|
||||
json.dump({'explain': plan, 'samples': samples, 'session_counters': metrics}, f, indent=2)
|
||||
@@ -0,0 +1,17 @@
|
||||
[
|
||||
{
|
||||
"remote": "/www/wwwroot/www.txiaw.com/index.php",
|
||||
"backup": "original-index.php",
|
||||
"sha256": "ad865f80571d23c8c84de7b77f29837c117b0c596abcb3e4e398f67dea7e0c36"
|
||||
},
|
||||
{
|
||||
"remote": "/www/server/panel/vhost/nginx/www.txiaw.com.conf",
|
||||
"backup": "original-www.txiaw.com.conf",
|
||||
"sha256": "580c609e64c520d47f7f2c3f1c763def70b8fb9baa81abcecf4f1aa72a6d011e"
|
||||
},
|
||||
{
|
||||
"remote": "/www/wwwroot/www.txiaw.com/Tpl/icp/gxl_index.html",
|
||||
"backup": "original-gxl_index.html",
|
||||
"sha256": "60fc73b14983aa9b6455792489a46ac221ed70b84f6a3e271214485adda0cc6b"
|
||||
}
|
||||
]
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,42 @@
|
||||
import json, os, stat, subprocess, tempfile, time
|
||||
base='/www/backup/txiaw-mysql-fix-20260831'
|
||||
directory='/www/server/panel/vhost/nginx'
|
||||
site=directory+'/www.txiaw.com.conf'
|
||||
nginx='/www/server/nginx/sbin/nginx'
|
||||
stamp=time.strftime('%Y%m%d-%H%M%S')
|
||||
backup=base+'/before-abuse-connection-close-'+stamp
|
||||
os.mkdir(backup,0o700)
|
||||
sources=PAYLOAD_FILES
|
||||
old={p:open(p,'rb').read() for p in [site]+[directory+'/'+x for x in sources]}
|
||||
for p,data in old.items():
|
||||
with open(backup+'/'+os.path.basename(p),'wb') as f:f.write(data)
|
||||
os.chmod(backup+'/'+os.path.basename(p),0o600)
|
||||
def put(p,data):
|
||||
st=os.stat(p);fd,tmp=tempfile.mkstemp(prefix='.txiaw-',dir=directory)
|
||||
try:
|
||||
with os.fdopen(fd,'wb') as f:f.write(data);f.flush();os.fsync(f.fileno())
|
||||
os.chmod(tmp,stat.S_IMODE(st.st_mode));os.chown(tmp,st.st_uid,st.st_gid);os.replace(tmp,p)
|
||||
finally:
|
||||
if os.path.exists(tmp):os.unlink(tmp)
|
||||
def run(args):
|
||||
return subprocess.check_output(args,stderr=subprocess.STDOUT,timeout=15).decode('utf-8','replace')
|
||||
configuration=old[site].decode('utf-8')
|
||||
http_include='include '+directory+'/txiaw-home-protection-http.inc;'
|
||||
server_include=' include '+directory+'/txiaw-home-protection-server.inc;'
|
||||
if http_include not in configuration:configuration=http_include+'\n'+configuration
|
||||
if server_include.strip() not in configuration:
|
||||
marker=' server_name www.txiaw.com txiaw.com;'
|
||||
if configuration.count(marker)!=1:raise RuntimeError('Unexpected server_name configuration; no files changed')
|
||||
configuration=configuration.replace(marker,marker+'\n'+server_include,1)
|
||||
try:
|
||||
for name,data in sources.items():put(directory+'/'+name,data.encode('utf-8'))
|
||||
put(site,configuration.encode('utf-8'))
|
||||
tested=run([nginx,'-t'])
|
||||
run([nginx,'-s','reload'])
|
||||
except Exception:
|
||||
for p,data in old.items():put(p,data)
|
||||
run([nginx,'-t'])
|
||||
raise
|
||||
r={'status':'confirmed_abuse_closed_without_http_response','request_rate_limits_enabled':False,'backup':backup,'nginx_test_ok':'successful' in tested}
|
||||
json.dump(r,open(base+'/abuse-connection-close.json','w'),indent=2)
|
||||
print(json.dumps(r),flush=True)
|
||||
@@ -0,0 +1,115 @@
|
||||
import hashlib
|
||||
import http.client
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
BASE = '/www/backup/txiaw-mysql-fix-20260831'
|
||||
STAGE = BASE + '/stage'
|
||||
WEB = '/www/wwwroot/www.txiaw.com'
|
||||
PHP = '/www/server/php/56/bin/php'
|
||||
TEMPLATE = WEB + '/Tpl/icp/gxl_index.html'
|
||||
HELPER = WEB + '/Lib/HomeNewsCache.php'
|
||||
RUNTIME = WEB + '/Runtime/TxiawHomeNews'
|
||||
QUERY = b"gxl_mysql_news('field:news_id,news_cid,news_name;limit:100;order:news_addtime desc')"
|
||||
REPLACEMENT = b"txiaw_home_news_cached()"
|
||||
|
||||
if not os.path.exists(BASE + '/index-result.json'):
|
||||
raise RuntimeError('Verify the new database index before enabling the cache')
|
||||
manifest = json.load(open(STAGE + '/baseline.json'))
|
||||
baseline = next(item for item in manifest if item['remote'] == TEMPLATE)
|
||||
original = open(TEMPLATE, 'rb').read()
|
||||
if hashlib.sha256(original).hexdigest() != baseline['sha256']:
|
||||
raise RuntimeError('Homepage template changed since inspection; refusing overwrite')
|
||||
if os.path.exists(HELPER):
|
||||
raise RuntimeError('Unexpected cache helper already exists')
|
||||
assert original.count(QUERY) == 1
|
||||
modified = original.replace(b'<php>$jishu =' + QUERY, b"<php>require_once APP_PATH . 'HomeNewsCache.php'; $jishu =" + REPLACEMENT, 1)
|
||||
assert modified != original and QUERY not in modified
|
||||
|
||||
for name in ['HomeNewsCache.php', 'test_home_news_cache.php']:
|
||||
subprocess.run([PHP, '-n', '-l', STAGE + '/' + name], check=True, timeout=10)
|
||||
unit_dir = tempfile.mkdtemp(prefix='cache-unit-', dir=BASE)
|
||||
subprocess.run([PHP, '-n', STAGE + '/test_home_news_cache.php', unit_dir], check=True, timeout=10)
|
||||
subprocess.run(['python3', STAGE + '/test_cache_concurrency.py'], check=True, timeout=15)
|
||||
|
||||
def atomic_write(data, target, metadata):
|
||||
fd, tmp = tempfile.mkstemp(prefix='.txiaw-repair-', suffix='.tmp', dir=os.path.dirname(target))
|
||||
try:
|
||||
with os.fdopen(fd, 'wb') as f:
|
||||
f.write(data)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
os.chmod(tmp, stat.S_IMODE(metadata.st_mode))
|
||||
os.chown(tmp, metadata.st_uid, metadata.st_gid)
|
||||
os.replace(tmp, target)
|
||||
finally:
|
||||
if os.path.exists(tmp):
|
||||
os.unlink(tmp)
|
||||
|
||||
template_info = os.stat(TEMPLATE)
|
||||
runtime_info = os.stat(WEB + '/Runtime')
|
||||
if not os.path.isdir(RUNTIME):
|
||||
os.mkdir(RUNTIME, 0o750)
|
||||
os.chown(RUNTIME, runtime_info.st_uid, runtime_info.st_gid)
|
||||
|
||||
compiled = []
|
||||
cache_root = os.path.realpath(WEB + '/Runtime/Cache')
|
||||
compiled_backup = BASE + '/compiled-home-before'
|
||||
os.makedirs(compiled_backup, mode=0o700, exist_ok=True)
|
||||
for root, dirs, files in os.walk(cache_root):
|
||||
dirs[:] = [d for d in dirs if not os.path.islink(os.path.join(root, d))]
|
||||
for name in files:
|
||||
path = os.path.join(root, name)
|
||||
if not name.endswith('.php') or os.path.islink(path) or os.path.getsize(path) > 2 * 1024 * 1024:
|
||||
continue
|
||||
if os.path.commonpath([cache_root, os.path.realpath(path)]) != cache_root:
|
||||
raise RuntimeError('Compiled-cache path escaped site cache root')
|
||||
data = open(path, 'rb').read()
|
||||
if QUERY in data:
|
||||
backup = compiled_backup + '/' + hashlib.sha256(path.encode()).hexdigest() + '.php'
|
||||
shutil.copy2(path, backup)
|
||||
info = os.stat(path)
|
||||
os.chmod(backup, 0o600)
|
||||
compiled.append((path, backup, info))
|
||||
|
||||
def request(path):
|
||||
c = http.client.HTTPConnection('127.0.0.1', 80, timeout=10)
|
||||
started = time.monotonic()
|
||||
c.request('GET', path, headers={'Host': 'www.txiaw.com', 'User-Agent': 'Codex-Repair-Check/1.0'})
|
||||
r = c.getresponse()
|
||||
data = r.read()
|
||||
result = {'path': path, 'status': r.status, 'bytes': len(data), 'seconds': round(time.monotonic() - started, 4)}
|
||||
c.close()
|
||||
if r.status != 200 or len(data) < 1000:
|
||||
raise RuntimeError('Homepage check failed: ' + str(result))
|
||||
return result
|
||||
|
||||
try:
|
||||
atomic_write(open(STAGE + '/HomeNewsCache.php', 'rb').read(), HELPER, os.stat(WEB + '/Lib/HomeRequestProtection.php'))
|
||||
atomic_write(modified, TEMPLATE, template_info)
|
||||
for path, backup, info in compiled:
|
||||
os.unlink(path)
|
||||
probes = [request('/')]
|
||||
cache_path = RUNTIME + '/news-list-v1.json'
|
||||
entry = json.load(open(cache_path))
|
||||
if len(entry['items']) != 100:
|
||||
raise RuntimeError('Cache did not preserve the expected 100 list items')
|
||||
digest = hashlib.sha256(open(cache_path, 'rb').read()).hexdigest()
|
||||
probes.append(request('/?r=codex-cache-key-check'))
|
||||
if digest != hashlib.sha256(open(cache_path, 'rb').read()).hexdigest():
|
||||
raise RuntimeError('Query parameter unexpectedly changed the fresh list cache')
|
||||
except Exception:
|
||||
atomic_write(original, TEMPLATE, template_info)
|
||||
# Keep the harmless helper available to any in-flight newly compiled template.
|
||||
for path, backup, info in compiled:
|
||||
atomic_write(open(backup, 'rb').read(), path, info)
|
||||
raise
|
||||
|
||||
result = {'status': 'installed', 'ttl_seconds': 60, 'stale_seconds': 300, 'items': len(entry['items']), 'invalidated_compiled_files': [p for p, _, _ in compiled], 'http_probes': probes, 'query_argument_reuses_cache': True}
|
||||
json.dump(result, open(BASE + '/cache-result.json', 'w'), indent=2)
|
||||
print('CACHE_DEPLOYED', json.dumps(result), flush=True)
|
||||
@@ -0,0 +1,78 @@
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
BASE = '/www/backup/txiaw-mysql-fix-20260831'
|
||||
STAGE = BASE + '/stage'
|
||||
WEB = '/www/wwwroot/www.txiaw.com'
|
||||
NGINX = '/www/server/panel/vhost/nginx'
|
||||
BIN = '/www/server/nginx/sbin/nginx'
|
||||
PHP = '/www/server/php/56/bin/php'
|
||||
|
||||
def run(args):
|
||||
subprocess.run(args, check=True, timeout=20)
|
||||
|
||||
def atomic_copy(src, dst, metadata=None):
|
||||
info = metadata or os.stat(src)
|
||||
fd, tmp = tempfile.mkstemp(prefix='.txiaw-repair-', suffix='.tmp', dir=os.path.dirname(dst))
|
||||
try:
|
||||
with os.fdopen(fd, 'wb') as target, open(src, 'rb') as source:
|
||||
shutil.copyfileobj(source, target)
|
||||
target.flush()
|
||||
os.fsync(target.fileno())
|
||||
os.chmod(tmp, stat.S_IMODE(info.st_mode))
|
||||
os.chown(tmp, info.st_uid, info.st_gid)
|
||||
os.replace(tmp, dst)
|
||||
finally:
|
||||
if os.path.exists(tmp):
|
||||
os.unlink(tmp)
|
||||
|
||||
manifest = json.load(open(STAGE + '/baseline.json'))
|
||||
for item in manifest:
|
||||
data = open(item['remote'], 'rb').read()
|
||||
if hashlib.sha256(data).hexdigest() != item['sha256']:
|
||||
raise RuntimeError('Source changed since inspection: ' + item['remote'])
|
||||
shutil.copy2(item['remote'], BASE + '/originals/' + item['backup'])
|
||||
os.chmod(BASE + '/originals/' + item['backup'], 0o600)
|
||||
|
||||
for name in ['HomeRequestProtection.php', 'index.php', 'test_home_guard.php']:
|
||||
run([PHP, '-l', STAGE + '/' + name])
|
||||
run([PHP, STAGE + '/test_home_guard.php'])
|
||||
|
||||
changes = [
|
||||
('HomeRequestProtection.php', WEB + '/Lib/HomeRequestProtection.php', None),
|
||||
('index.php', WEB + '/index.php', 'original-index.php'),
|
||||
('txiaw-home-protection-http.inc', NGINX + '/txiaw-home-protection-http.inc', None),
|
||||
('txiaw-home-protection-server.inc', NGINX + '/txiaw-home-protection-server.inc', None),
|
||||
('www.txiaw.com.conf', NGINX + '/www.txiaw.com.conf', 'original-www.txiaw.com.conf'),
|
||||
]
|
||||
for source, target, backup in changes:
|
||||
if backup is None and os.path.exists(target):
|
||||
raise RuntimeError('Refusing to overwrite unexpected file: ' + target)
|
||||
|
||||
installed = []
|
||||
metadata = {}
|
||||
try:
|
||||
for source, target, backup in changes:
|
||||
metadata[target] = os.stat(target) if backup else None
|
||||
if not backup:
|
||||
os.chmod(STAGE + '/' + source, 0o644)
|
||||
atomic_copy(STAGE + '/' + source, target, metadata[target])
|
||||
installed.append((target, backup))
|
||||
run([BIN, '-t'])
|
||||
run([BIN, '-s', 'reload'])
|
||||
except Exception:
|
||||
for target, backup in reversed(installed):
|
||||
if backup:
|
||||
atomic_copy(BASE + '/originals/' + backup, target, metadata[target])
|
||||
else:
|
||||
os.unlink(target)
|
||||
run([BIN, '-t'])
|
||||
raise
|
||||
|
||||
json.dump({'files': [p for p, _ in installed], 'status': 'installed'}, open(BASE + '/guard-installed.json', 'w'), indent=2)
|
||||
print('GUARD_DEPLOYED; PHP guard passed; nginx configuration passed and gracefully reloaded.', flush=True)
|
||||
@@ -0,0 +1,25 @@
|
||||
import os,subprocess,json,http.client,ssl,time,glob,re
|
||||
result={'time':time.strftime('%Y-%m-%d %H:%M:%S')}
|
||||
for name,args in [('uptime',['uptime']),('memory',['free','-m']),('listener',['ss','-ltnp','sport = :8686']),('bt_status',['/etc/init.d/bt','status'])]:
|
||||
try:
|
||||
p=subprocess.run(args,stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True,timeout=5)
|
||||
result[name]=(p.stdout+p.stderr)[:1600]
|
||||
except Exception as e:result[name]=type(e).__name__+': '+str(e)
|
||||
print(json.dumps(result),flush=True)
|
||||
for protocol in ['https','http']:
|
||||
started=time.monotonic()
|
||||
try:
|
||||
c=http.client.HTTPSConnection('127.0.0.1',8686,timeout=5,context=ssl._create_unverified_context()) if protocol=='https' else http.client.HTTPConnection('127.0.0.1',8686,timeout=5)
|
||||
c.request('GET','/gaorui',headers={'Host':'47.106.181.28:8686','User-Agent':'Codex-Panel-Diagnostic/1.0'})
|
||||
r=c.getresponse();body=r.read(500)
|
||||
print(json.dumps({'local_protocol':protocol,'status':r.status,'location':r.getheader('Location'),'prefix':body[:180].decode('utf-8','replace'),'seconds':round(time.monotonic()-started,2)}),flush=True)
|
||||
c.close()
|
||||
except Exception as e:print(json.dumps({'local_protocol':protocol,'error':type(e).__name__+': '+str(e),'seconds':round(time.monotonic()-started,2)}),flush=True)
|
||||
print('PANEL_LOG_METADATA',flush=True)
|
||||
for p in ['/www/server/panel/logs/error.log','/www/server/panel/logs/requests.log','/www/server/panel/logs/task.log','/www/server/panel/logs/panel.log']:
|
||||
if os.path.exists(p):print(json.dumps({'path':p,'size':os.path.getsize(p),'modified':time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(os.path.getmtime(p)))}),flush=True)
|
||||
for p in ['/www/server/panel/logs/error.log','/www/server/panel/logs/panel.log']:
|
||||
if os.path.exists(p):
|
||||
text=subprocess.check_output(['tail','-n','25',p]).decode('utf-8','replace')
|
||||
text=re.sub(r'(?i)((?:password|passwd|token|secret|cookie|authorization)\s*[=:]\s*)[^\s,;]+',r'\1<redacted>',text)
|
||||
print(json.dumps({'log':p,'tail':text[-4000:]}),flush=True)
|
||||
@@ -0,0 +1,24 @@
|
||||
import os,subprocess,time,json,collections
|
||||
result={}
|
||||
for p in ['/proc/sys/net/netfilter/nf_conntrack_count','/proc/sys/net/netfilter/nf_conntrack_max']:
|
||||
if os.path.exists(p):result[p.rsplit('/',1)[-1]]=open(p).read().strip()
|
||||
for name,args in [('socket_summary',['ss','-s']),('panel_process',['ps','-p','737470,737482','-o','pid,ppid,lstart,etime,%cpu,%mem,rss,comm']),('routes',['ip','route']),('queue',['tc','-s','qdisc','show'])]:
|
||||
try:result[name]=subprocess.check_output(args,stderr=subprocess.STDOUT,universal_newlines=True,timeout=4)[:1800]
|
||||
except Exception as e:result[name]=type(e).__name__
|
||||
def network():
|
||||
d={}
|
||||
for l in open('/proc/net/dev'):
|
||||
if ':' in l:
|
||||
name,fields=l.split(':',1);v=list(map(int,fields.split()));d[name.strip()]=v
|
||||
return d
|
||||
start=network();t=time.monotonic();time.sleep(3);end=network();dt=time.monotonic()-t
|
||||
result['network_per_second']={k:{'rx_Mbps':round((v[0]-start[k][0])*8/dt/1e6,3),'tx_Mbps':round((v[8]-start[k][8])*8/dt/1e6,3),'rx_drop_delta':v[3]-start[k][3],'tx_drop_delta':v[11]-start[k][11]} for k,v in end.items() if k!='lo'}
|
||||
try:
|
||||
p=subprocess.run(['dmesg','--ctime'],stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True,timeout=4)
|
||||
result['kernel_recent_resource_messages']=[l for l in p.stdout.splitlines()[-500:] if any(x in l.lower() for x in ['conntrack','out of memory','oom-kill','blocked for','net_ratelimit'])][-8:]
|
||||
except Exception:pass
|
||||
try:
|
||||
p=subprocess.run(['iptables','-S'],stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True,timeout=4)
|
||||
result['firewall_relevant_rules']=[l for l in p.stdout.splitlines() if '8686' in l or l.startswith('-P') or ('-j DROP' in l and '--dport' not in l)][:15]
|
||||
except Exception:pass
|
||||
print(json.dumps(result),flush=True)
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/Lib/HomeRequestProtection.php';
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2014 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
//session_start();
|
||||
|
||||
// 应用入口文件
|
||||
error_reporting(E_ALL & ~E_NOTICE);
|
||||
// 检测PHP环境
|
||||
if(version_compare(PHP_VERSION,'5.4.0','<')) die('require PHP > 5.4.0 !');
|
||||
//超时时间
|
||||
@set_time_limit(300);
|
||||
//内存限制 取消内存限制
|
||||
@ini_set("memory_limit",'-1');
|
||||
// 开启调试模式 建议开发阶段开启 部署阶段注释或者设为false
|
||||
//define('APP_DEBUG',True);
|
||||
// 定义应用目录
|
||||
define('APP_PATH','./Lib/');
|
||||
|
||||
// 引入ThinkPHP入口文件
|
||||
require './Lib/ThinkPHP/ThinkPHP.php';
|
||||
// 亲^_^ 后面不需要任何代码了 就是如此简单
|
||||
@@ -0,0 +1,26 @@
|
||||
[
|
||||
{
|
||||
"route": "normal",
|
||||
"port": 80,
|
||||
"status": 200,
|
||||
"bytes_read": 16848,
|
||||
"seconds": 0.66,
|
||||
"title": "PC\u6e38\u620f\u4e0b\u8f7d\uff0c\u5b89\u5353\u6e38\u620f\u4e0b\u8f7d\uff0c\u624b\u673a\u6e38\u620f\u4e0b\u8f7d\uff0c\u624b\u673a\u8f6f\u4ef6\u4e0b\u8f7d\uff0c\u5b89\u5353\u8f6f\u4ef6\u4e0b\u8f7d\uff0c\u9e3f\u8499\u8f6f\u4ef6\u4e0b\u8f7d\uff0c\u5b83\u4e0b\u7f51"
|
||||
},
|
||||
{
|
||||
"route": "normal",
|
||||
"port": 8686,
|
||||
"status": 200,
|
||||
"bytes_read": 25502,
|
||||
"seconds": 0.7,
|
||||
"title": "\u5b9d\u5854Linux\u9762\u677f"
|
||||
},
|
||||
{
|
||||
"route": "proxy",
|
||||
"port": 8686,
|
||||
"status": 200,
|
||||
"bytes_read": 25502,
|
||||
"seconds": 0.58,
|
||||
"title": "\u5b9d\u5854Linux\u9762\u677f"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
{"time": "2026-08-31 17:42:43", "nginx_test_ok": true, "abuse_drop_rule_loaded": true, "txiaw_home_frequency_limits_loaded": false, "uptime": "17:42:43 up 1:08, 0 users, load average: 3.50, 3.28, 3.06", "panel_listener": "State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess\nLISTEN 0 100 0.0.0.0:8686 0.0.0.0:* users:((\"BT-Panel\",pid=1272,fd=3))", "panel_processes": "PID STAT %CPU RSS NLWP WCHAN COMMAND\n 1272 S 0.0 64052 1 - BT-Panel\n 1349 Sl 0.0 13084 6 - BT-Task", "tcp_by_service_and_state": {"8686 LISTEN": 1, "80 LISTEN": 1, "22 LISTEN": 2, "443 LISTEN": 1, "80 FIN-WAIT-1": 2607, "80 FIN-WAIT-2": 463, "80 TIME-WAIT": 4027, "443 TIME-WAIT": 50, "80 ESTAB": 484, "80 CLOSING": 11, "80 SYN-RECV": 10, "80 LAST-ACK": 25, "443 ESTAB": 31, "443 FIN-WAIT-2": 1, "443 SYN-RECV": 1, "443 FIN-WAIT-1": 3, "8686 ESTAB": 3, "443 CLOSING": 2, "22 ESTAB": 2, "3306 LISTEN": 1, "3306 TIME-WAIT": 24, "3306 ESTAB": 1}, "network_Mbps": {"eth0": {"rx": 2.99, "tx": 3.55}}, "cpu_idle_pct": 54.0, "home": {"status": 200, "bytes": 16848, "seconds": 0.02}, "article": {"status": 200, "bytes": 31198, "seconds": 0.03}, "observed_abuse": {"result": "RemoteDisconnected", "detail": "Remote end closed connection without response", "seconds": 0.0}, "other_site": {"status": 200, "bytes": 14578, "seconds": 0.03}, "panel": {"phase": "http", "first_line": "HTTP/1.1 200 OK", "seconds": 0.08, "bytes_received": 342}, "recent_access_statuses": {"444": 982, "200": 18}, "recent_access_time_range": ["31/Aug/2026:17:42:38 +0800", "31/Aug/2026:17:42:45 +0800"], "logged_response_body_bytes": 152036, "temporary_filter_set": "Name: txiaw_cc_0831\nType: hash:ip\nRevision: 4\nHeader: family inet hashsize 1024 maxelem 4096 timeout 900\nSize in memory: 8168\nReferences: 1\nNumber of entries: 84\n", "temporary_filter_counters": ["67556 4043K DROP tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 match-set txiaw_cc_0831 src /* txiaw-confirmed-abuse-expiring */"]}
|
||||
@@ -0,0 +1,87 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>{$sitename}</title>
|
||||
<meta name="keywords" content="{$keywords}" />
|
||||
<meta name="description" content="{$description}" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
|
||||
<link href="/Public/del/css/global.css" rel="stylesheet" />
|
||||
<script type="text/javascript" src="/kan/js/jquery-1.8.3.min.js"></script>
|
||||
<link rel="stylesheet" href="/layui26/index.css">
|
||||
<link rel="stylesheet" href="/Public/del/img/index.css" />
|
||||
<link rel="stylesheet" href="/Public/code/css/main.css" />
|
||||
<script language="JavaScript" type="text/javascript" >Sid=1;Id=''</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<include file="gxl:head" />
|
||||
|
||||
</div>
|
||||
|
||||
<div class="jshuw">
|
||||
|
||||
<php>$jishu =gxl_mysql_news('field:news_id,news_cid,news_name;limit:100;order:news_addtime desc');</php>
|
||||
<div class="lis">
|
||||
|
||||
<ul>
|
||||
|
||||
<volist name="jishu" id="gxl" offset="0" length="10" >
|
||||
<li><a href="{$gxl.news_readurl}" target="_blank" title='{$gxl.news_name}'>{$gxl.news_name|ltrim}</a></li>
|
||||
</volist>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="lis">
|
||||
|
||||
<ul>
|
||||
|
||||
<volist name="jishu" id="gxl" offset="10" length="10" >
|
||||
<li><a href="{$gxl.news_readurl}" target="_blank" title="{$gxl.news_name}">{$gxl.news_name|ltrim}</a></li>
|
||||
</volist>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="lis">
|
||||
|
||||
<ul>
|
||||
|
||||
<volist name="jishu" id="gxl" offset="20" length="10">
|
||||
<li><a href="{$gxl.news_readurl}" target="_blank" title="{$gxl.news_name}">{$gxl.news_name|ltrim}</a></li>
|
||||
</volist>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="lis">
|
||||
|
||||
<ul>
|
||||
|
||||
<volist name="jishu" id="gxl" offset="30" length="10" >
|
||||
<li><a href="{$gxl.news_readurl}" target="_blank" title="{$gxl.news_name}">{$gxl.news_name|ltrim}</a></li>
|
||||
</volist>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<notempty name="list_link">
|
||||
<div class="flinks mt20">
|
||||
<div class="fhd"><h3>友情链接</h3><span class="links_click"><a class="r_top"></a><a class="r_bottom"></a></span></div>
|
||||
<div class="fbox idx-fbox">
|
||||
<div id="links_box">
|
||||
<volist name="list_link" id="gxl">
|
||||
<a href=" {$gxl.link_url}" target="_blank"> {$gxl.link_name}</a>
|
||||
|
||||
</volist>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</notempty>
|
||||
|
||||
|
||||
<include file="gxl:fend" />
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2014 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
//session_start();
|
||||
|
||||
// 应用入口文件
|
||||
error_reporting(E_ALL & ~E_NOTICE);
|
||||
// 检测PHP环境
|
||||
if(version_compare(PHP_VERSION,'5.4.0','<')) die('require PHP > 5.4.0 !');
|
||||
//超时时间
|
||||
@set_time_limit(300);
|
||||
//内存限制 取消内存限制
|
||||
@ini_set("memory_limit",'-1');
|
||||
// 开启调试模式 建议开发阶段开启 部署阶段注释或者设为false
|
||||
//define('APP_DEBUG',True);
|
||||
// 定义应用目录
|
||||
define('APP_PATH','./Lib/');
|
||||
|
||||
// 引入ThinkPHP入口文件
|
||||
require './Lib/ThinkPHP/ThinkPHP.php';
|
||||
// 亲^_^ 后面不需要任何代码了 就是如此简单
|
||||
@@ -0,0 +1,79 @@
|
||||
server
|
||||
{
|
||||
listen 80;
|
||||
listen 443 ssl;
|
||||
http2 on;
|
||||
server_name www.txiaw.com txiaw.com;
|
||||
index index.php index.html index.htm default.php default.htm default.html;
|
||||
root /www/wwwroot/www.txiaw.com;
|
||||
#CERT-APPLY-CHECK--START
|
||||
# 用于SSL证书申请时的文件验证相关配置 -- 请勿删除
|
||||
include /www/server/panel/vhost/nginx/well-known/www.txiaw.com.conf;
|
||||
#CERT-APPLY-CHECK--END
|
||||
include /www/server/panel/vhost/nginx/extension/www.txiaw.com/*.conf;
|
||||
|
||||
#SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则
|
||||
#error_page 404/404.html;
|
||||
ssl_certificate /www/server/panel/vhost/cert/www.txiaw.com/fullchain.pem;
|
||||
ssl_certificate_key /www/server/panel/vhost/cert/www.txiaw.com/privkey.pem;
|
||||
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_session_tickets on;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
add_header Strict-Transport-Security "max-age=31536000";
|
||||
error_page 497 https://$host$request_uri;
|
||||
|
||||
#SSL-END
|
||||
|
||||
#ERROR-PAGE-START 错误页配置,可以注释、删除或修改
|
||||
error_page 404 /404.html;
|
||||
#error_page 502 /502.html;
|
||||
#ERROR-PAGE-END
|
||||
|
||||
#PHP-INFO-START PHP引用配置,可以注释或修改
|
||||
include enable-php-56.conf;
|
||||
#PHP-INFO-END
|
||||
|
||||
#REWRITE-START URL重写规则引用,修改后将导致面板设置的伪静态规则失效
|
||||
include /www/server/panel/vhost/rewrite/www.txiaw.com.conf;
|
||||
#REWRITE-END
|
||||
|
||||
# 禁止访问的敏感文件
|
||||
location ~* (\.user.ini|\.htaccess|\.htpasswd|\.env.*|\.project|\.bashrc|\.bash_profile|\.bash_logout|\.DS_Store|\.gitignore|\.gitattributes|LICENSE|README\.md|CLAUDE\.md|CHANGELOG\.md|CHANGELOG|CONTRIBUTING\.md|TODO\.md|FAQ\.md|composer\.json|composer\.lock|package(-lock)?\.json|yarn\.lock|pnpm-lock\.yaml|\.\w+~|\.swp|\.swo|\.bak(up)?|\.old|\.tmp|\.temp|\.log|\.sql(\.gz)?|docker-compose\.yml|docker\.env|Dockerfile|\.csproj|\.sln|Cargo\.toml|Cargo\.lock|go\.mod|go\.sum|phpunit\.xml|phpunit\.xml|pom\.xml|build\.gradl|pyproject\.toml|requirements\.txt|application(-\w+)?\.(ya?ml|properties))$
|
||||
{
|
||||
return 404;
|
||||
}
|
||||
|
||||
# 禁止访问的敏感目录
|
||||
location ~* /(\.git|\.svn|\.bzr|\.vscode|\.claude|\.idea|\.ssh|\.github|\.npm|\.yarn|\.pnpm|\.cache|\.husky|\.turbo|\.next|\.nuxt|node_modules|runtime)/ {
|
||||
return 404;
|
||||
}
|
||||
|
||||
#一键申请SSL证书验证目录相关设置
|
||||
location ~ \.well-known{
|
||||
allow all;
|
||||
}
|
||||
|
||||
#禁止在证书验证目录放入敏感文件
|
||||
if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) {
|
||||
return 403;
|
||||
}
|
||||
|
||||
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
|
||||
{
|
||||
expires 30d;
|
||||
error_log /dev/null;
|
||||
access_log /dev/null;
|
||||
}
|
||||
|
||||
location ~ .*\.(js|css)?$
|
||||
{
|
||||
expires 12h;
|
||||
error_log /dev/null;
|
||||
access_log /dev/null;
|
||||
}
|
||||
access_log /www/wwwlogs/www.txiaw.com.log;
|
||||
error_log /www/wwwlogs/www.txiaw.com.error.log;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"route": "normal",
|
||||
"seconds": 5.02,
|
||||
"error": "_ssl.c:989: The handshake operation timed out"
|
||||
},
|
||||
{
|
||||
"route": "SOCKS",
|
||||
"seconds": 5.0,
|
||||
"error": "_ssl.c:989: The handshake operation timed out"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,32 @@
|
||||
import os, subprocess, time, json, collections, http.client, ssl
|
||||
r={'time':time.strftime('%Y-%m-%d %H:%M:%S')}
|
||||
def run(args):
|
||||
return subprocess.check_output(args,stderr=subprocess.STDOUT,universal_newlines=True,timeout=6)
|
||||
for name,args in [('uptime',['uptime']),('queue',['tc','-s','qdisc','show']),('panel_process',['ps','-p','737470,737482','-o','pid,lstart,etime,%cpu,%mem,comm'])]:
|
||||
try:r[name]=run(args)[:1500]
|
||||
except Exception as e:r[name]=str(e)
|
||||
try:
|
||||
lines=run(['ss','-tan']).splitlines()[1:]
|
||||
r['tcp_by_port_and_state']=dict(collections.Counter(l.split()[3].rsplit(':',1)[-1]+' '+l.split()[0] for l in lines if len(l.split())>4))
|
||||
except Exception as e:r['ss_error']=str(e)
|
||||
try:
|
||||
r['firewall']=[l for l in run(['iptables','-S']).splitlines() if '8686' in l or l.startswith('-P') or ('-j DROP' in l and '--dport' not in l)][:25]
|
||||
except Exception as e:r['firewall_error']=str(e)
|
||||
def net():
|
||||
return {l.split(':')[0].strip():list(map(int,l.split(':')[1].split())) for l in open('/proc/net/dev') if ':' in l}
|
||||
a=net();started=time.monotonic();time.sleep(3);b=net();dt=time.monotonic()-started
|
||||
r['network_Mbps']={k:{'rx':round((v[0]-a[k][0])*8/dt/1e6,2),'tx':round((v[8]-a[k][8])*8/dt/1e6,2),'rx_drops':v[3]-a[k][3],'tx_drops':v[11]-a[k][11]} for k,v in b.items() if k!='lo'}
|
||||
p='/www/wwwlogs/www.txiaw.com.log'
|
||||
if os.path.isfile(p):
|
||||
lines=run(['tail','-n','2000',p]).splitlines()
|
||||
r['access_last2000']={'status':dict(collections.Counter(l.split('"')[2].split()[0] for l in lines if l.count('"')>=3)), 'first_time':lines[0].split('[',1)[-1].split(']',1)[0], 'last_time':lines[-1].split('[',1)[-1].split(']',1)[0], 'bytes':sum(int(l.split('"')[2].split()[1]) for l in lines if l.count('"')>=3 and l.split('"')[2].split()[1].isdigit())}
|
||||
for path in ['/www/server/panel/data/admin_path.pl','/www/server/panel/data/limitip.conf','/www/server/panel/data/domain.conf','/www/server/panel/data/ssl.pl']:
|
||||
if os.path.exists(path):r[path]=open(path).read()[:500]
|
||||
for target in ['/gaorui','/gaorui/']:
|
||||
try:
|
||||
c=http.client.HTTPSConnection('127.0.0.1',8686,timeout=4,context=ssl._create_unverified_context())
|
||||
c.request('GET',target,headers={'Host':'47.106.181.28:8686','User-Agent':'Mozilla/5.0'})
|
||||
q=c.getresponse();data=q.read();r['loopback'+target]={'status':q.status,'bytes':len(data),'title':data.decode('utf-8','replace').split('<title>')[-1].split('</title>')[0][:100]};c.close()
|
||||
except Exception as e:r['loopback'+target]={'error':str(e)}
|
||||
json.dump(r,open('/www/backup/txiaw-mysql-fix-20260831/panel-detail.json','w'),ensure_ascii=False)
|
||||
print(json.dumps(r,ensure_ascii=False),flush=True)
|
||||
@@ -0,0 +1,12 @@
|
||||
import os,time,json,subprocess
|
||||
r={}
|
||||
def net():
|
||||
return {l.split(':')[0].strip():list(map(int,l.split(':')[1].split())) for l in open('/proc/net/dev') if ':' in l}
|
||||
a=net();time.sleep(2);b=net()
|
||||
r['network_Mbps']={k:{'rx':round((v[0]-a[k][0])*4/1e6,2),'tx':round((v[8]-a[k][8])*4/1e6,2)} for k,v in b.items() if k!='lo'}
|
||||
for name in ['nf_conntrack_count','nf_conntrack_max']:
|
||||
p='/proc/sys/net/netfilter/'+name
|
||||
if os.path.exists(p):r[name]=open(p).read().strip()
|
||||
r['socket_summary']=subprocess.check_output(['ss','-s'],universal_newlines=True,timeout=3)[:700]
|
||||
json.dump(r,open('/www/backup/txiaw-mysql-fix-20260831/panel-network.json','w'))
|
||||
print(json.dumps(r),flush=True)
|
||||
@@ -0,0 +1 @@
|
||||
python3 -c "import gzip,base64;exec(gzip.decompress(base64.b64decode('H4sIALVGlWoC/51VTY/bNhC961fwYpBCLdl1gSDYwLcs0ByCHHZ7KBYLgZZGK8YSyZBUZCPIf+8Mafkju0WBCrBNkTNvOG/mjdVgjQvsqzd6yYxfMt+NQfX4G2TA73FnnanB40mAwbaqB1ypAbJsJz2wLeOraZpWO1nvR7sKByWnYjj6b33RqkOxWW/erd//8TvPgnQvEM72Htx3cCsrNfSr753xYaVflD7QYRlRytoM+NEtz4xTeCZ79I5Bf0OQec+fV8VbrhH016gpkt8pnZY8w2wHi2aUWemDa2kh+OLvxbBoisWfi8+LB55nXkvrOxOuL3ITtdhBaxwUTgYoejWoUDjT98ROwdE8xUG30/WUrvuxAY+ATxnD5z/YSfx2ZoAC6xKgDsroogvBlgjFl/8fI5mfUJ6zrIGWuVELLJvP7yKugzA6fdUTZd1Bva/MGOwYoiX1TQPOba+MHh4/fvnrMXUNmm4367xsoDYNEjyGtnjPl4w7sL2sATmOkWUwg6qryakAopFBLlnjw+keAwSJhBlfUpMKOoj7bYNBUhlPrVoOe09rYR1gO255qtRVTRBXuS1CWRm6EtdaYuEJMmEGd0xB6ZlU6Chs2xgLWlA8Pu14zqRnmNnFkB7cKC/3z1+dtf3oO5F/iID+qGsRd/HW2oj8Ym+I5sE0AlNLsiwfqk+fv3y8F0QEclDhKeQJqe7MpJPpfDqq5vLyopob6BPvyePCJOmpv8pctWzmCA7KB08O+R1tjrpXeh/fcSbE9qfiEEFJ81RdJAlDyUbkmWwDOBIQ5+VXo7RAd2DoxuJCaZZASm97FWjPi0c3Qk6XoFfSp7IiZxqFiOZJOIKftMRIZ1FjHzAo6NRo0tdKYaUojiWvWXnZc54h8HzxLYv3S5lbp3QQNBvLZhysFz848T96fsejlirSeRV1Xs2Alewp02Mldx504D/zJYu13sYskpKkwunxcKTmvD+oINY4W+Lgxblgj5szc/PAybNzG8xbS7Y279boeG7RG9HENFAPEejUy+BpcpGqn+L1sTJF4M+nO91s+yTK3siGDOBQgw3sPv7guHgjYGLwNuK/haL0s8yBH3u60Y80tmZu2WtySbDQVKTZecjNPKD9mZJ0krwp28rsCc6PNU2iduw5lZ5Oyt5M4ER+cjHOdlLPNaxIhb7CeSex3zDsGCpstorYgAYRn+zrRrrWiPKEIGz+vMx+ZucGEinjZVLH+R/kjT+LknyoBBPH9lG6wU7abvLsVUcmxF9a7B9yCCnh1QcAAA==')))"
|
||||
@@ -0,0 +1 @@
|
||||
{"status": "nginx_rate_limit_rolled_back", "snapshot": "/www/backup/txiaw-mysql-fix-20260831/www.txiaw.com-before-rate-limit-rollback-20260831-171842.conf", "nginx_test_ok": true, "orphan_include_files_retained_but_not_loaded": ["/www/server/panel/vhost/nginx/txiaw-home-protection-http.inc", "/www/server/panel/vhost/nginx/txiaw-home-protection-server.inc"]}
|
||||
@@ -0,0 +1,20 @@
|
||||
[
|
||||
{
|
||||
"route": "normal",
|
||||
"port": 80,
|
||||
"error": "timed out",
|
||||
"seconds": 10.02
|
||||
},
|
||||
{
|
||||
"route": "normal",
|
||||
"port": 8686,
|
||||
"error": "_ssl.c:989: The handshake operation timed out",
|
||||
"seconds": 10.02
|
||||
},
|
||||
{
|
||||
"route": "proxy",
|
||||
"port": 8686,
|
||||
"error": "_ssl.c:989: The handshake operation timed out",
|
||||
"seconds": 10.02
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
{"time": "2026-08-31 17:35:08", "nginx_test_ok": true, "loaded_txiaw_home_rules": 0, "bt_status": "\u001b[32mBt-Panel (pid 1272) already running\u001b[0m\n\u001b[32mBt-Task (pid 1349) already running\u001b[0m", "uptime": "17:35:09 up 1:00, 0 users, load average: 2.41, 3.78, 3.04", "network_Mbps": {"eth0": {"rx": 4.71, "tx": 5.11}}, "cpu_idle_pct": 62.0, "socket_summary": "Total: 16101\nTCP: 18853 (estab 14338, closed 3068, orphaned 1412, timewait 3068)\n\nTransport Total IP IPv6\nRAW\t 1 1 0 \nUDP\t 4 3 1 \nTCP\t 15785 15773 12 \nINET\t 15790 15777 13 \nFRAG\t 0 0 0 \n\n", "home": {"status": 200, "bytes": 16848, "seconds": 1.1, "title": "PC\u6e38\u620f\u4e0b\u8f7d\uff0c\u5b89\u5353\u6e38\u620f\u4e0b\u8f7d\uff0c\u624b\u673a\u6e38\u620f\u4e0b\u8f7d\uff0c\u624b\u673a\u8f6f\u4ef6\u4e0b\u8f7d\uff0c\u5b89\u5353\u8f6f\u4ef6\u4e0b\u8f7d\uff0c\u9e3f\u8499\u8f6f\u4ef6\u4e0b\u8f7d\uff0c\u5b83\u4e0b\u7f51"}, "observed_abuse_signature": {"status": 429, "bytes": 39, "seconds": 1.01, "title": null}, "panel": {"error": "timed out"}, "mysql_status": "Variable_name\tValue\nSlow_queries\t20\nThreads_connected\t17\nThreads_running\t1\n", "news_index": "Table\tNon_unique\tKey_name\tSeq_in_index\tColumn_name\tCollation\tCardinality\tSub_part\tPacked\tNull\tIndex_type\tComment\tIndex_comment\ngxl_news\t1\tidx_news_status_addtime\t1\tnews_status\tA\t2\tNULL\tNULL\t\tBTREE\t\t\ngxl_news\t1\tidx_news_status_addtime\t2\tnews_addtime\tA\t65600\tNULL\tNULL\t\tBTREE\t\t\n", "recent_access_statuses": {"429": 999, "499": 1}, "recent_access_time_range": ["31/Aug/2026:17:35:19 +0800", "31/Aug/2026:17:35:21 +0800"], "observed_abuse_signature_count": 999}
|
||||
@@ -0,0 +1 @@
|
||||
python3 -c 'import sys,gzip,base64;exec(gzip.decompress(base64.b64decode(sys.argv[1])))' H4sIAGpKlWoC/41Wa3PbthL9rl+BZsYDMuFDkhNFly7TSRM3ziRO2ki5bkfVcCASolCRAA2AtlyP/3t3Qb3ctHeubVnkYhc42MfZFXWjtCXKBOQPo2RANA+IaReNVjk3ILWiBkmuqornVigJopW1TZRXgksLuqbqLZjhKY1vb2/jBcvXbRPbjWC3YX1nrqtwKTbhsD8c9cenA9rT6T3FPWmC/yNj9RIfPHryW3hShycFOblITi6Tkwn1H3oFXxLdSo/p0gRc3qSflOR+0iPwo7lttTwCG+Urnq8z1dqmtZ2JsQXXOj3SmUzffv46dXvBJ8CzwSAdDP2o4LkqAElrl+GYBlTzpmI5p35PlimimHV3NFzfcB3LUshNbBZCdo9gEU7p3O/pGXWCzHJjM7Wm85TiIxEG0OYIY9lWlAhJZInalWIFLzLntGylap7ptuIG7GQZ5aqV1qOHRepOWNjMWGZb1Npi4zaPhRQ2KuKFBTS7dR+9LBrP2bWN8/7OaPd60EGXS269x16+ryLTVAKAJNSf9ec79aQSxno1azwB2fBIaTDfvvm+T5ZKkwpvrBouPRpjOGI4Ji74DfWJWBKwwfXqocdSd/6ZTV2K1Eoqq6TIQXRTb3Hf1Hg7uOQAPqeA/2yxtSq+NQutiwm3t0qvs8tFg067Xyf3VG9oosHBhefdwK1CNlvP4dt/Oo4LGw/4KBj6Afj+SGu81Ro/1npwV1wHN3iJRSQsr43nLrb+LoUI04eeVrcmne18dOSTm7qTVUJyMJqFw2Q+R8h502aiqHjW5BYgm7ZGN3vVbPB8fmSPG/txBY51T2hpVL7mkCJtXTN9tw+3MZilmBOz5GW/P++1LKWX6k9RVSx+EfWJdyVkAZuQT1My6Ef9MwKC0fMzshk998nrpqn4FV98EDZ+cfoyOh0R78PF9PJjQCqx5uQd1J/yyZuVhjyNB6f/ifr4SyZsybTYmtAeApcMeAW5J2iYXQUrZWzQQmWxEmgFLzXzqMv2YNwPaAywofQiVwVQEjUNWuYHHlULV41FxhZgnRlRSkh6vTP7QacaqAtZKSwEKyUcI/JvN6MUN2uY5BVYjsYjsC2Z0q2Atecvo0F/FA3Gg2g4TnDVnT7vCiR3nPQPyepWrb7r1DrVI+qMLqbTnydvlJQdsXp0MHzp/AUp7Ryz46ZRkCtp+camQLZRlmvOLM9aCSQklgLuvl2GQsN8Q9s0RZiEV4aTv5/5fx3pH0BHml+3QF8efXc+pdtwcWAsbYDLL8CjNHHho18hFuFrDCBN9rF8OGx1neZRya3mpoFGwqFYF6q4S6/hBFZgwUMJ8FRDV+BM5ytvoen3TvbKi57+4H8fdy80QLMA9CaHvfUMMwrKekd7yXXUPQV0cQf0SxMsELSEijZA9bIwu7L+ljC6usfjsFHBV1SCauMN/kebQOc73c7vmBYPDh/f5Lyx5Nx9gd8JM4QnB8TQopSmCVCqx/3OZCkkq6qj3IG98ySHOCp0XC9flul9fQBVqVuugY53otOOHmpHDzyC7QqgJO1pSumMPpl7xSKbsfDPbP7Mx/ffzdP0Ffzz8MV37v59CLrBlq+x78FHK2XjR7UTf4H2BO6LIa2WMXh1KcqoWTXU30YV4/QeboUdtxC59ZSJ4FlomDYuf5v88jH7+eptCvfB1PAo4GpuCxo8EjBjgLoLCp2kh309fdyJ3ZwRYyN2T0hxoVQhdDLWVtZRXkufwY4z3A0zk85BtjrIMH+dLMy76gh3lfACrX+kjo7d7o/bLqJ5NqMhEM6TycXnK/Lu4+cfX38kk+nr6dcJubo4/3JO/gvkxxZA4xhw8v4T8eh0hd4x0OilFLKEQ3aSLQAOPqATCGsGxacF5K9/9mSOc0s3YPBbk0FQ+ebfgLz/9Pb8V/LTl8+XBGIFf1G5qTK026L6wO8coJSKYuMWtlfLWFG4qWB/oGtM2xZimXAelth8+/0+fO2yo1KleZwdkJelGy6O2hvC1zwHasiYG4a2x7qBx2XI0bQZvcHxBxJ3P1o8gdFiuB8toFkf+qA7ACul2k1NoPxqSJgsyL/Y+/8AB++eaSZLHJP2/RrKJhhAb96PNXTuBMcAZg4BiILuAZTncwfIIXO0MJv3cNKOirZuPN3VFw7Qz2isgdfDStTChvuutSP5CI2wcUGnwrhLmw79XqNxHtjvZzztB8uqNat0qlvu9/4C1DSrid8LAAA=
|
||||
@@ -0,0 +1,54 @@
|
||||
import json, os, shutil, stat, subprocess, tempfile, time
|
||||
|
||||
base = '/www/backup/txiaw-mysql-fix-20260831'
|
||||
target = '/www/server/panel/vhost/nginx/www.txiaw.com.conf'
|
||||
original = base + '/originals/original-www.txiaw.com.conf'
|
||||
nginx = '/www/server/nginx/sbin/nginx'
|
||||
stamp = time.strftime('%Y%m%d-%H%M%S')
|
||||
snapshot = base + '/www.txiaw.com-before-rate-limit-rollback-' + stamp + '.conf'
|
||||
includes = [
|
||||
'/www/server/panel/vhost/nginx/txiaw-home-protection-http.inc',
|
||||
'/www/server/panel/vhost/nginx/txiaw-home-protection-server.inc',
|
||||
]
|
||||
|
||||
def run(args):
|
||||
return subprocess.check_output(args, stderr=subprocess.STDOUT, timeout=20).decode('utf-8', 'replace')
|
||||
|
||||
def atomic_write(data, dst):
|
||||
meta = os.stat(dst)
|
||||
fd, tmp = tempfile.mkstemp(prefix='.txiaw-rollback-', dir=os.path.dirname(dst))
|
||||
try:
|
||||
with os.fdopen(fd, 'wb') as out:
|
||||
out.write(data)
|
||||
out.flush(); os.fsync(out.fileno())
|
||||
os.chmod(tmp, stat.S_IMODE(meta.st_mode)); os.chown(tmp, meta.st_uid, meta.st_gid)
|
||||
os.replace(tmp, dst)
|
||||
finally:
|
||||
if os.path.exists(tmp): os.unlink(tmp)
|
||||
|
||||
before = open(target, 'rb').read()
|
||||
after = b''.join(line for line in before.splitlines(True) if line.strip() not in [
|
||||
('include ' + p + ';').encode('ascii') for p in includes
|
||||
])
|
||||
if before == after:
|
||||
print(json.dumps({'status':'nginx_rate_limit_includes_already_absent'}), flush=True)
|
||||
raise SystemExit(0)
|
||||
shutil.copy2(target, snapshot)
|
||||
os.chmod(snapshot, 0o600)
|
||||
try:
|
||||
atomic_write(after, target)
|
||||
test = run([nginx, '-t'])
|
||||
run([nginx, '-s', 'reload'])
|
||||
except Exception:
|
||||
atomic_write(before, target)
|
||||
run([nginx, '-t'])
|
||||
raise
|
||||
|
||||
result = {
|
||||
'status': 'nginx_rate_limit_rolled_back',
|
||||
'snapshot': snapshot,
|
||||
'nginx_test_ok': 'successful' in test.lower(),
|
||||
'orphan_include_files_retained_but_not_loaded': [p for p in includes if os.path.isfile(p)],
|
||||
}
|
||||
json.dump(result, open(base + '/rate-limit-rollback.json', 'w'), indent=2)
|
||||
print(json.dumps(result), flush=True)
|
||||
@@ -0,0 +1 @@
|
||||
{"status": "active", "blocked_sources": 84, "ttl_seconds": 900, "port": 80, "record": "/www/backup/txiaw-mysql-fix-20260831/temporary-abuse-source-filter.json", "scope": "only confirmed high-rate sources also present in live TCP peers; no SSH or panel port rule"}
|
||||
@@ -0,0 +1 @@
|
||||
{"activated_at": "2026-08-31 17:42:02", "source_count": 84, "remaining_seconds_range": [773, 774], "estimated_last_expiry": "2026-08-31 17:57:01", "automatic_refresh_configured": false}
|
||||
@@ -0,0 +1,56 @@
|
||||
import os, json, time, subprocess, collections, datetime, re, ipaddress
|
||||
base='/www/backup/txiaw-mysql-fix-20260831'
|
||||
name='txiaw_cc_0831'
|
||||
ttl=900
|
||||
marker=base+'/temporary-abuse-source-filter.json'
|
||||
def run(args):return subprocess.check_output(args,stderr=subprocess.STDOUT,timeout=12).decode('utf-8','replace')
|
||||
lines=run(['tail','-n','4000','/www/wwwlogs/www.txiaw.com.log']).splitlines()
|
||||
abuse=collections.Counter();total=collections.Counter();dates=[]
|
||||
for line in lines:
|
||||
a=line.split('"')
|
||||
if not line.split():continue
|
||||
ip=line.split()[0];total[ip]+=1
|
||||
try:dates.append(datetime.datetime.strptime(line.split('[',1)[1].split(']',1)[0],'%d/%b/%Y:%H:%M:%S %z').timestamp())
|
||||
except (ValueError,IndexError):pass
|
||||
if len(a)>5 and a[5] in ('','-') and a[2].split()[0]=='444' and re.match(r'^(GET|HEAD) /(?:index[.]php)?[?](?:[^ ]*&)?r(?:=|&| )',a[1]):abuse[ip]+=1
|
||||
if not dates or time.time()-max(dates)>30 or max(dates)-min(dates)>30:
|
||||
raise RuntimeError('Abuse sample is not recent enough; no firewall changes')
|
||||
span=max(max(dates)-min(dates),1)
|
||||
live=set()
|
||||
for line in run(['ss','-tan']).splitlines()[1:]:
|
||||
a=line.split()
|
||||
if len(a)>4 and a[3].rsplit(':',1)[-1]=='80' and a[0]!='LISTEN':live.add(a[4].rsplit(':',1)[0].strip('[]'))
|
||||
excluded={'47.106.181.28'}
|
||||
excluded.update(os.environ.get('SSH_CONNECTION','').split()[:1])
|
||||
sources=[]
|
||||
for ip,n in abuse.items():
|
||||
try:address=ipaddress.ip_address(ip)
|
||||
except ValueError:continue
|
||||
if address.version==4 and address.is_global and ip in live and ip not in excluded and n>=20 and n/total[ip]>=0.98 and n/span>=5:sources.append(ip)
|
||||
sources=sorted(sources)
|
||||
if not sources:raise RuntimeError('No qualifying live abuse sources; no firewall changes')
|
||||
existing=set(run(['ipset','list','-name']).splitlines())
|
||||
if name in existing:
|
||||
if os.path.isfile(marker):
|
||||
print(json.dumps({'status':'filter_already_exists','record':marker}),flush=True)
|
||||
raise SystemExit(0)
|
||||
raise RuntimeError('Unexpected existing ipset; no firewall changes')
|
||||
rule=['-p','tcp','--dport','80','-m','set','--match-set',name,'src','-m','comment','--comment','txiaw-confirmed-abuse-expiring','-j','DROP']
|
||||
record={'status':'preparing','time':time.strftime('%Y-%m-%d %H:%M:%S'),'set':name,'ttl_seconds':ttl,'port':80,'sources':sources,'sample_seconds':span,'abuse_requests':sum(abuse.values()),'live_source_count':len(live)}
|
||||
with open(marker,'w') as f:json.dump(record,f,indent=2)
|
||||
os.chmod(marker,0o600)
|
||||
created=False;inserted=False
|
||||
try:
|
||||
run(['ipset','create',name,'hash:ip','family','inet','hashsize','1024','maxelem','4096','timeout',str(ttl)]);created=True
|
||||
for ip in sources:run(['ipset','add',name,ip,'timeout',str(ttl)])
|
||||
run(['iptables','-w','5','-I','INPUT','1']+rule);inserted=True
|
||||
run(['iptables','-w','5','-C','INPUT']+rule)
|
||||
record['status']='active';record['activated_at']=time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
with open(marker,'w') as f:json.dump(record,f,indent=2)
|
||||
except Exception:
|
||||
if inserted:run(['iptables','-w','5','-D','INPUT']+rule)
|
||||
if created:run(['ipset','destroy',name])
|
||||
record['status']='rolled_back_after_error'
|
||||
with open(marker,'w') as f:json.dump(record,f,indent=2)
|
||||
raise
|
||||
print(json.dumps({'status':record['status'],'blocked_sources':len(sources),'ttl_seconds':ttl,'port':80,'record':marker,'scope':'only confirmed high-rate sources also present in live TCP peers; no SSH or panel port rule'}),flush=True)
|
||||
@@ -0,0 +1,30 @@
|
||||
import concurrent.futures
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
base = '/www/backup/txiaw-mysql-fix-20260831'
|
||||
root = tempfile.mkdtemp(prefix='cache-concurrency-', dir=base)
|
||||
php = '/www/server/php/56/bin/php'
|
||||
source = r'''
|
||||
require $argv[1];
|
||||
$directory = $argv[2];
|
||||
$loader = function () use ($directory) {
|
||||
file_put_contents($directory . '/loader-calls', "called\n", FILE_APPEND | LOCK_EX);
|
||||
usleep(100000);
|
||||
return array(array('news_id' => 42, 'news_name' => 'Concurrency fixture'));
|
||||
};
|
||||
$result = txiaw_home_news_cached($directory, $loader);
|
||||
if ($result[0]['news_id'] !== 42) { exit(2); }
|
||||
echo "ok\n";
|
||||
'''
|
||||
|
||||
def worker(_):
|
||||
return subprocess.run([php, '-n', '-r', source, base + '/stage/HomeNewsCache.php', root], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, timeout=5)
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=8) as pool:
|
||||
results = list(pool.map(worker, range(8)))
|
||||
assert all(p.returncode == 0 and p.stdout.strip() == 'ok' for p in results), [(p.returncode, p.stdout, p.stderr) for p in results]
|
||||
calls = open(root + '/loader-calls').read().splitlines()
|
||||
assert len(calls) == 1, 'Concurrent cold requests executed %s loader calls' % len(calls)
|
||||
print('PASS: 8 simultaneous cold requests returned the same data with exactly 1 loader call.')
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
require __DIR__ . '/HomeRequestProtection.php';
|
||||
|
||||
function check_guard($method, $uri, $ua, $query, $expected)
|
||||
{
|
||||
$server = array('REQUEST_METHOD' => $method, 'REQUEST_URI' => $uri);
|
||||
if ($ua !== null) {
|
||||
$server['HTTP_USER_AGENT'] = $ua;
|
||||
}
|
||||
if (txiaw_should_block_home_request($server, $query) !== $expected) {
|
||||
fwrite(STDERR, 'Failed request guard: ' . $method . ' ' . $uri . "\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
check_guard('GET', '/?r=123', null, array('r' => '123'), true);
|
||||
check_guard('HEAD', '/?r=123', '', array('r' => '123'), true);
|
||||
check_guard('GET', '/index.php?x=1&r=123', ' ', array('x' => '1', 'r' => '123'), true);
|
||||
check_guard('GET', '/?r=', '', array('r' => ''), true);
|
||||
check_guard('GET', '/?r[]=123', '', array('r' => array('123')), true);
|
||||
check_guard('GET', '/?r=123', 'Mozilla/5.0', array('r' => '123'), false);
|
||||
check_guard('GET', '/', null, array(), false);
|
||||
check_guard('GET', '/?utm_source=test', null, array('utm_source' => 'test'), false);
|
||||
check_guard('GET', '/down/184778.html?r=1', null, array('r' => '1'), false);
|
||||
check_guard('POST', '/index.php?r=1', null, array('r' => '1'), false);
|
||||
check_guard('GET', '/.well-known/acme-challenge/test?r=1', null, array('r' => '1'), false);
|
||||
echo "PASS: 11 guard cases; normal homepage, browser, article, POST and ACME requests preserved.\n";
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
require __DIR__ . '/HomeNewsCache.php';
|
||||
$directory = $argv[1];
|
||||
if (!is_dir($directory)) {
|
||||
mkdir($directory, 0700, true);
|
||||
}
|
||||
$file = $directory . '/news-list-v1.json';
|
||||
$calls = 0;
|
||||
$loader = function () use (&$calls) {
|
||||
$calls++;
|
||||
return array(array('news_id' => $calls, 'news_name' => 'Public article'));
|
||||
};
|
||||
function expect_cache($condition, $message)
|
||||
{
|
||||
if (!$condition) {
|
||||
throw new Exception($message);
|
||||
}
|
||||
}
|
||||
$first = txiaw_home_news_cached($directory, $loader);
|
||||
$second = txiaw_home_news_cached($directory, $loader);
|
||||
expect_cache($first === $second && $calls === 1, 'Fresh cache must avoid a second query');
|
||||
|
||||
file_put_contents($file, json_encode(array('created' => time() - 61, 'items' => $first)));
|
||||
$third = txiaw_home_news_cached($directory, $loader);
|
||||
expect_cache($calls === 2 && $third[0]['news_id'] === 2, 'Expired cache must refresh');
|
||||
|
||||
file_put_contents($file, json_encode(array('created' => time() - 61, 'items' => $third)));
|
||||
$held = fopen($directory . '/news-list-v1.lock', 'c');
|
||||
flock($held, LOCK_EX);
|
||||
$started = microtime(true);
|
||||
$stale = txiaw_home_news_cached($directory, $loader);
|
||||
expect_cache($stale === $third && $calls === 2, 'Busy refresher must serve existing stale data');
|
||||
expect_cache(microtime(true) - $started < 0.1, 'Stale read must not wait for the lock');
|
||||
flock($held, LOCK_UN);
|
||||
fclose($held);
|
||||
|
||||
$failedLoader = function () { throw new Exception('Simulated database failure'); };
|
||||
expect_cache(txiaw_home_news_cached($directory, $failedLoader) === $third, 'Recent stale cache should survive a transient database failure');
|
||||
|
||||
file_put_contents($file, '{corrupt');
|
||||
$recovered = txiaw_home_news_cached($directory, $loader);
|
||||
expect_cache($calls === 3 && $recovered[0]['news_id'] === 3, 'Corrupt cache must recover');
|
||||
expect_cache(txiaw_read_home_news_cache($file) !== false, 'Recovery must write a valid cache');
|
||||
echo "PASS: cache hit, TTL refresh, nonblocking stale read, transient error fallback, corrupt cache recovery.\n";
|
||||
@@ -0,0 +1,14 @@
|
||||
# Loaded in http context, only referenced by the txiaw virtual host.
|
||||
# Original request_uri is used so rewritten article URLs are not homepage traffic.
|
||||
map $request_uri $txiaw_home_path {
|
||||
default 0;
|
||||
~^/(index[.]php)?([?]|$) 1;
|
||||
}
|
||||
map $args $txiaw_home_random_arg {
|
||||
default 0;
|
||||
~(^|&)r(=|&|$) 1;
|
||||
}
|
||||
map "$request_method|$txiaw_home_path|$txiaw_home_random_arg|$http_user_agent" $txiaw_home_cc_block {
|
||||
default 0;
|
||||
"~^(GET|HEAD)[|]1[|]1[|][[:space:]]*$" 1;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
# Only the confirmed no-UA random-r homepage flood is closed before PHP.
|
||||
# No per-IP or aggregate homepage request-rate limit is enabled here.
|
||||
if ($txiaw_home_cc_block) {
|
||||
return 444;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import os, subprocess, json, time, collections, http.client, ssl, re, socket
|
||||
base='/www/backup/txiaw-mysql-fix-20260831'
|
||||
r={'time':time.strftime('%Y-%m-%d %H:%M:%S')}
|
||||
def run(args):return subprocess.check_output(args,stderr=subprocess.STDOUT,timeout=10).decode('utf-8','replace')
|
||||
conf=run(['/www/server/nginx/sbin/nginx','-T'])
|
||||
r['nginx_test_ok']='test is successful' in conf
|
||||
r['abuse_drop_rule_loaded']='if ($txiaw_home_cc_block)' in conf and 'return 444;' in conf
|
||||
r['txiaw_home_frequency_limits_loaded']='zone=txiaw_home_per_ip' in conf or 'zone=txiaw_home_total' in conf
|
||||
r['uptime']=run(['uptime']).strip()
|
||||
r['panel_listener']=run(['ss','-ltnp','sport = :8686']).strip()
|
||||
r['panel_processes']=run(['ps','-C','BT-Panel,BT-Task','-o','pid,stat,%cpu,rss,nlwp,wchan:25,comm']).strip()
|
||||
tcp=run(['ss','-tan']).splitlines()[1:]
|
||||
r['tcp_by_service_and_state']=dict(collections.Counter(l.split()[3].rsplit(':',1)[-1]+' '+l.split()[0] for l in tcp if len(l.split())>4 and l.split()[3].rsplit(':',1)[-1] in ('22','80','443','8686','3306')))
|
||||
def net():return {l.split(':')[0].strip():list(map(int,l.split(':')[1].split())) for l in open('/proc/net/dev') if ':' in l}
|
||||
a=net();started=time.monotonic();vm=run(['vmstat','1','3']);b=net();dt=time.monotonic()-started
|
||||
r['network_Mbps']={k:{'rx':round((v[0]-a[k][0])*8/dt/1e6,2),'tx':round((v[8]-a[k][8])*8/dt/1e6,2)} for k,v in b.items() if k!='lo'}
|
||||
r['cpu_idle_pct']=sum(int(x.split()[14]) for x in vm.splitlines()[-2:])/2
|
||||
ua='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36'
|
||||
for name,path,host,agent in [('home','/','www.txiaw.com',ua),('article','/down/184778.html','www.txiaw.com',ua),('observed_abuse','/?r=connection-close-check','www.txiaw.com',''),('other_site','/','www.xxiaw.com',ua)]:
|
||||
c=http.client.HTTPConnection('127.0.0.1',80,timeout=4);t=time.monotonic()
|
||||
try:
|
||||
c.request('GET',path,headers={'Host':host,'User-Agent':agent});q=c.getresponse();body=q.read()
|
||||
r[name]={'status':q.status,'bytes':len(body),'seconds':round(time.monotonic()-t,2)}
|
||||
except Exception as e:r[name]={'result':type(e).__name__,'detail':str(e),'seconds':round(time.monotonic()-t,2)}
|
||||
finally:c.close()
|
||||
s=None;phase='tcp';t=time.monotonic()
|
||||
try:
|
||||
s=socket.create_connection(('127.0.0.1',8686),4);s.settimeout(4);phase='tls'
|
||||
s=ssl._create_unverified_context().wrap_socket(s,server_hostname='47.106.181.28');phase='http'
|
||||
s.sendall(('GET /gaorui HTTP/1.1\r\nHost: 47.106.181.28:8686\r\nUser-Agent: '+ua+'\r\nConnection: close\r\n\r\n').encode())
|
||||
data=s.recv(8192);r['panel']={'phase':phase,'first_line':data.split(b'\r\n')[0].decode('utf-8','replace'),'seconds':round(time.monotonic()-t,2),'bytes_received':len(data)}
|
||||
except Exception as e:r['panel']={'phase':phase,'error':str(e),'seconds':round(time.monotonic()-t,2)}
|
||||
finally:
|
||||
if s:s.close()
|
||||
lines=run(['tail','-n','1000','/www/wwwlogs/www.txiaw.com.log']).splitlines()
|
||||
r['recent_access_statuses']=dict(collections.Counter(l.split('"')[2].split()[0] for l in lines if l.count('"')>2 and l.split('"')[2].split()))
|
||||
r['recent_access_time_range']=[l.split('[',1)[-1].split(']',1)[0] for l in [lines[0],lines[-1]]] if lines else []
|
||||
r['logged_response_body_bytes']=sum(int(l.split('"')[2].split()[1]) for l in lines if len(l.split('"'))>2 and len(l.split('"')[2].split())>1 and l.split('"')[2].split()[1].isdigit())
|
||||
json.dump(r,open(base+'/abuse-connection-close-verified.json','w'),indent=2)
|
||||
print(json.dumps(r),flush=True)
|
||||
@@ -0,0 +1,39 @@
|
||||
import os, json, re, subprocess, time, collections, http.client, ssl
|
||||
base='/www/backup/txiaw-mysql-fix-20260831'
|
||||
r={'time':time.strftime('%Y-%m-%d %H:%M:%S')}
|
||||
def run(args,env=None):
|
||||
return subprocess.check_output(args,stderr=subprocess.STDOUT,env=env,timeout=12).decode('utf-8','replace')
|
||||
ng=run(['/www/server/nginx/sbin/nginx','-T'])
|
||||
r['nginx_test_ok']='test is successful' in ng
|
||||
r['loaded_txiaw_home_rules']=ng.count('txiaw_home')
|
||||
r['bt_status']=run(['/etc/init.d/bt','status']).strip()
|
||||
r['uptime']=run(['uptime']).strip()
|
||||
def net():
|
||||
return {l.split(':')[0].strip():list(map(int,l.split(':')[1].split())) for l in open('/proc/net/dev') if ':' in l}
|
||||
a=net();t=time.monotonic();vm=run(['vmstat','1','3']);b=net();dt=time.monotonic()-t
|
||||
r['network_Mbps']={k:{'rx':round((v[0]-a[k][0])*8/dt/1e6,2),'tx':round((v[8]-a[k][8])*8/dt/1e6,2)} for k,v in b.items() if k!='lo'}
|
||||
rows=[l.split() for l in vm.splitlines()[-2:]]
|
||||
r['cpu_idle_pct']=sum(int(l[14]) for l in rows)/len(rows)
|
||||
r['socket_summary']=run(['ss','-s'])[:700]
|
||||
ua='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36'
|
||||
for name,port,path,host,useragent in [('home',80,'/','www.txiaw.com',ua),('observed_abuse_signature',80,'/?r=rollback-diagnostic','www.txiaw.com',''),('panel',8686,'/gaorui','47.106.181.28:8686',ua)]:
|
||||
c=None;t=time.monotonic()
|
||||
try:
|
||||
c=http.client.HTTPSConnection('127.0.0.1',port,timeout=6,context=ssl._create_unverified_context()) if port==8686 else http.client.HTTPConnection('127.0.0.1',port,timeout=6)
|
||||
c.request('GET',path,headers={'Host':host,'User-Agent':useragent})
|
||||
q=c.getresponse();body=q.read();title=re.search(br'<title>(.*?)</title>',body,re.S)
|
||||
r[name]={'status':q.status,'bytes':len(body),'seconds':round(time.monotonic()-t,2),'title':title.group(1).decode('utf-8','replace') if title else None}
|
||||
except Exception as e:r[name]={'error':str(e)}
|
||||
finally:
|
||||
if c:c.close()
|
||||
cfg={m.group(1).lower():m.group(3) for m in re.finditer(r'''['"](db_[a-z_]+)['"]\s*=>\s*(['"])(.*?)\2''',open('/www/wwwroot/www.txiaw.com/Runtime/Conf/config.php').read(),re.I)}
|
||||
env=dict(os.environ,MYSQL_PWD=cfg.get('db_pwd',cfg.get('db_password')))
|
||||
args=['/www/server/mysql/bin/mysql','--no-defaults','-u'+cfg['db_user'],'-h'+cfg['db_host'],'--connect-timeout=5','-B']
|
||||
r['mysql_status']=run(args+['-e',"SHOW GLOBAL STATUS WHERE Variable_name IN ('Threads_running','Threads_connected','Slow_queries');"],env)
|
||||
r['news_index']=run(args+['-e',"SHOW INDEX FROM xiaxia.gxl_news WHERE Key_name='idx_news_status_addtime';"],env)
|
||||
lines=run(['tail','-n','1000','/www/wwwlogs/www.txiaw.com.log']).splitlines()
|
||||
r['recent_access_statuses']=dict(collections.Counter(l.split('"')[2].split()[0] for l in lines if l.count('"')>2 and l.split('"')[2].split()))
|
||||
r['recent_access_time_range']=[l.split('[',1)[-1].split(']',1)[0] for l in [lines[0],lines[-1]]] if lines else []
|
||||
r['observed_abuse_signature_count']=sum(1 for l in lines if len(l.split('"'))>5 and l.split('"')[5] in ('','-') and re.match(r'^(GET|HEAD) /(?:index[.]php)?[?](?:[^ ]*&)?r(?:=|&| )',l.split('"')[1]))
|
||||
json.dump(r,open(base+'/rate-limit-rollback-verified.json','w'),indent=2)
|
||||
print(json.dumps(r),flush=True)
|
||||
@@ -0,0 +1,96 @@
|
||||
import collections
|
||||
import concurrent.futures
|
||||
import datetime
|
||||
import hashlib
|
||||
import http.client
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
BASE = '/www/backup/txiaw-mysql-fix-20260831'
|
||||
|
||||
def probe_http(host, path, ua='Codex-Repair-Check/1.0'):
|
||||
started = time.monotonic()
|
||||
c = http.client.HTTPConnection('127.0.0.1', 80, timeout=10)
|
||||
c.request('GET', path, headers={'Host': host, 'User-Agent': ua})
|
||||
r = c.getresponse()
|
||||
data = r.read()
|
||||
result = {'host': host, 'path': path, 'status': r.status, 'bytes': len(data), 'seconds': round(time.monotonic() - started, 4)}
|
||||
c.close()
|
||||
return result
|
||||
|
||||
checks = []
|
||||
for host, path, ua, expected in [
|
||||
('www.txiaw.com', '/?r=codex-final-guard', '', [429]),
|
||||
('www.txiaw.com', '/', 'Codex-Repair-Check/1.0', [200]),
|
||||
('www.txiaw.com', '/down/184778.html', 'Codex-Repair-Check/1.0', [200]),
|
||||
('www.xxiaw.com', '/', 'Codex-Repair-Check/1.0', [200]),
|
||||
('m.bchongw.com', '/', 'Codex-Repair-Check/1.0', [200, 301, 302]),
|
||||
]:
|
||||
result = probe_http(host, path, ua)
|
||||
checks.append(result)
|
||||
if result['status'] not in expected:
|
||||
raise RuntimeError('Page regression: ' + str(result))
|
||||
print('PAGE_CHECKS', json.dumps(checks), flush=True)
|
||||
|
||||
# A bounded 30-request check verifies the limiter, not a stress test.
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as pool:
|
||||
burst = list(pool.map(lambda i: probe_http('www.txiaw.com', '/?guard_test=' + str(i)), range(30)))
|
||||
statuses = collections.Counter(p['status'] for p in burst)
|
||||
if not statuses.get(429) or any(k not in [200, 429] for k in statuses):
|
||||
raise RuntimeError('Homepage rate limiter did not behave as expected: ' + str(statuses))
|
||||
unaffected = [probe_http('www.txiaw.com', '/down/184778.html'), probe_http('www.xxiaw.com', '/')]
|
||||
if any(p['status'] != 200 for p in unaffected):
|
||||
raise RuntimeError('Homepage limits affected unrelated pages: ' + str(unaffected))
|
||||
time.sleep(5)
|
||||
recovery = probe_http('www.txiaw.com', '/')
|
||||
if recovery['status'] != 200:
|
||||
raise RuntimeError('Rate limit did not recover: ' + str(recovery))
|
||||
print('RATE_LIMIT_CHECK', dict(statuses), 'unaffected', json.dumps(unaffected), 'recovery', json.dumps(recovery), flush=True)
|
||||
|
||||
config = open('/www/wwwroot/www.txiaw.com/Runtime/Conf/config.php', encoding='utf-8').read()
|
||||
cfg = {m.group(1).lower(): m.group(3) for m in re.finditer(r'''['"](db_[a-z_]+)['"]\s*=>\s*(['"])(.*?)\2''', config, re.I)}
|
||||
env = dict(os.environ, MYSQL_PWD=cfg.get('db_pwd', cfg.get('db_password')))
|
||||
mysql = ['/www/server/mysql/bin/mysql', '--no-defaults', '-u' + cfg['db_user'], '-h' + cfg['db_host'], '--connect-timeout=5', '-B']
|
||||
|
||||
def sql(q):
|
||||
p = subprocess.run(mysql + ['-e', q], env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, timeout=10)
|
||||
if p.returncode:
|
||||
raise RuntimeError(p.stderr[:500])
|
||||
return p.stdout
|
||||
|
||||
query = "SHOW GLOBAL STATUS WHERE Variable_name IN ('Threads_connected','Threads_running','Slow_queries','Questions','Uptime','Innodb_row_lock_current_waits');"
|
||||
a = sql(query)
|
||||
started = time.monotonic()
|
||||
time.sleep(5)
|
||||
b = sql(query)
|
||||
elapsed = time.monotonic() - started
|
||||
|
||||
def stats(raw):
|
||||
return {p[0]: int(p[1]) for p in (line.split('\t') for line in raw.splitlines()[1:]) if len(p) == 2}
|
||||
|
||||
before, after = stats(a), stats(b)
|
||||
per_second = {k: round((after[k] - before[k]) / elapsed, 3) for k in ['Slow_queries', 'Questions']}
|
||||
print('MYSQL_STATUS', json.dumps(after), 'PER_SECOND', json.dumps(per_second), flush=True)
|
||||
|
||||
cpu = subprocess.check_output(['pidstat', '-u', '-p', '535252', '1', '3'], universal_newlines=True)
|
||||
vm = subprocess.check_output(['vmstat', '1', '3'], universal_newlines=True)
|
||||
up = subprocess.check_output(['uptime'], universal_newlines=True).strip()
|
||||
print('UPTIME', up, flush=True)
|
||||
print('MYSQL_CPU\n' + cpu, flush=True)
|
||||
print('VMSTAT\n' + vm, flush=True)
|
||||
|
||||
lines = subprocess.check_output(['tail', '-n', '3000', '/www/wwwlogs/www.txiaw.com.log']).decode('utf-8', 'replace').splitlines()
|
||||
log_status = collections.Counter()
|
||||
for line in lines:
|
||||
parts = line.split('"')
|
||||
if len(parts) > 2 and parts[2].split():
|
||||
log_status[parts[2].split()[0]] += 1
|
||||
print('LATEST_LOG_STATUS_SAMPLE', dict(log_status), flush=True)
|
||||
|
||||
subprocess.run(['/www/server/nginx/sbin/nginx', '-t'], check=True, timeout=10)
|
||||
result = {'time': datetime.datetime.now().isoformat(), 'page_checks': checks, 'burst_statuses': dict(statuses), 'unaffected_by_homepage_limit': unaffected, 'rate_limit_recovery': recovery, 'mysql_status': after, 'mysql_per_second': per_second, 'uptime': up, 'mysql_cpu': cpu, 'vmstat': vm, 'recent_access_status_sample': dict(log_status)}
|
||||
json.dump(result, open(BASE + '/verification-result.json', 'w'), indent=2)
|
||||
print('VERIFICATION_COMPLETE', flush=True)
|
||||
@@ -0,0 +1,23 @@
|
||||
import json,os,subprocess,collections,datetime
|
||||
base='/www/backup/txiaw-mysql-fix-20260831'
|
||||
p=base+'/verification-result.json'
|
||||
result={'time':datetime.datetime.now().isoformat(),'full_verification_saved':os.path.exists(p)}
|
||||
if os.path.exists(p):
|
||||
d=json.load(open(p));result['saved_time']=d['time'];result['mysql_status']=d['mysql_status'];result['burst_statuses']=d['burst_statuses'];result['recent_access_status_sample']=d['recent_access_status_sample']
|
||||
vm=subprocess.check_output(['vmstat','1','3'],universal_newlines=True).splitlines()
|
||||
rows=[r.split() for r in vm[-2:]]
|
||||
result['cpu_sample']={'idle_pct':sum(int(r[14]) for r in rows)/len(rows),'io_wait_pct':sum(int(r[15]) for r in rows)/len(rows)}
|
||||
result['uptime']=subprocess.check_output(['uptime'],universal_newlines=True).strip()
|
||||
test=subprocess.run(['/www/server/nginx/sbin/nginx','-t'],stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True,timeout=10)
|
||||
result['nginx_config_ok']=test.returncode==0
|
||||
result['index_record_saved']=os.path.exists(base+'/index-result.json')
|
||||
result['cache_record_saved']=os.path.exists(base+'/cache-result.json')
|
||||
result['backup_bytes']=os.path.getsize(base+'/gxl_news-before.sql.gz')
|
||||
lines=subprocess.check_output(['tail','-n','1000','/www/wwwlogs/www.txiaw.com.log']).decode('utf-8','replace').splitlines()
|
||||
c=collections.Counter()
|
||||
for l in lines:
|
||||
a=l.split('"')
|
||||
if len(a)>2 and a[2].split():c[a[2].split()[0]]+=1
|
||||
result['last_1000_statuses']=dict(c)
|
||||
json.dump(result,open(base+'/final-summary.json','w'),indent=2)
|
||||
print(json.dumps(result),flush=True)
|
||||
@@ -0,0 +1,81 @@
|
||||
include /www/server/panel/vhost/nginx/txiaw-home-protection-http.inc;
|
||||
server
|
||||
{
|
||||
listen 80;
|
||||
listen 443 ssl;
|
||||
http2 on;
|
||||
server_name www.txiaw.com txiaw.com;
|
||||
include /www/server/panel/vhost/nginx/txiaw-home-protection-server.inc;
|
||||
index index.php index.html index.htm default.php default.htm default.html;
|
||||
root /www/wwwroot/www.txiaw.com;
|
||||
#CERT-APPLY-CHECK--START
|
||||
# 用于SSL证书申请时的文件验证相关配置 -- 请勿删除
|
||||
include /www/server/panel/vhost/nginx/well-known/www.txiaw.com.conf;
|
||||
#CERT-APPLY-CHECK--END
|
||||
include /www/server/panel/vhost/nginx/extension/www.txiaw.com/*.conf;
|
||||
|
||||
#SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则
|
||||
#error_page 404/404.html;
|
||||
ssl_certificate /www/server/panel/vhost/cert/www.txiaw.com/fullchain.pem;
|
||||
ssl_certificate_key /www/server/panel/vhost/cert/www.txiaw.com/privkey.pem;
|
||||
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_session_tickets on;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
add_header Strict-Transport-Security "max-age=31536000";
|
||||
error_page 497 https://$host$request_uri;
|
||||
|
||||
#SSL-END
|
||||
|
||||
#ERROR-PAGE-START 错误页配置,可以注释、删除或修改
|
||||
error_page 404 /404.html;
|
||||
#error_page 502 /502.html;
|
||||
#ERROR-PAGE-END
|
||||
|
||||
#PHP-INFO-START PHP引用配置,可以注释或修改
|
||||
include enable-php-56.conf;
|
||||
#PHP-INFO-END
|
||||
|
||||
#REWRITE-START URL重写规则引用,修改后将导致面板设置的伪静态规则失效
|
||||
include /www/server/panel/vhost/rewrite/www.txiaw.com.conf;
|
||||
#REWRITE-END
|
||||
|
||||
# 禁止访问的敏感文件
|
||||
location ~* (\.user.ini|\.htaccess|\.htpasswd|\.env.*|\.project|\.bashrc|\.bash_profile|\.bash_logout|\.DS_Store|\.gitignore|\.gitattributes|LICENSE|README\.md|CLAUDE\.md|CHANGELOG\.md|CHANGELOG|CONTRIBUTING\.md|TODO\.md|FAQ\.md|composer\.json|composer\.lock|package(-lock)?\.json|yarn\.lock|pnpm-lock\.yaml|\.\w+~|\.swp|\.swo|\.bak(up)?|\.old|\.tmp|\.temp|\.log|\.sql(\.gz)?|docker-compose\.yml|docker\.env|Dockerfile|\.csproj|\.sln|Cargo\.toml|Cargo\.lock|go\.mod|go\.sum|phpunit\.xml|phpunit\.xml|pom\.xml|build\.gradl|pyproject\.toml|requirements\.txt|application(-\w+)?\.(ya?ml|properties))$
|
||||
{
|
||||
return 404;
|
||||
}
|
||||
|
||||
# 禁止访问的敏感目录
|
||||
location ~* /(\.git|\.svn|\.bzr|\.vscode|\.claude|\.idea|\.ssh|\.github|\.npm|\.yarn|\.pnpm|\.cache|\.husky|\.turbo|\.next|\.nuxt|node_modules|runtime)/ {
|
||||
return 404;
|
||||
}
|
||||
|
||||
#一键申请SSL证书验证目录相关设置
|
||||
location ~ \.well-known{
|
||||
allow all;
|
||||
}
|
||||
|
||||
#禁止在证书验证目录放入敏感文件
|
||||
if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) {
|
||||
return 403;
|
||||
}
|
||||
|
||||
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
|
||||
{
|
||||
expires 30d;
|
||||
error_log /dev/null;
|
||||
access_log /dev/null;
|
||||
}
|
||||
|
||||
location ~ .*\.(js|css)?$
|
||||
{
|
||||
expires 12h;
|
||||
error_log /dev/null;
|
||||
access_log /dev/null;
|
||||
}
|
||||
access_log /www/wwwlogs/www.txiaw.com.log;
|
||||
error_log /www/wwwlogs/www.txiaw.com.error.log;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
{"lines": 5000, "range": ["01/Sep/2026:14:18:17 +0800", "01/Sep/2026:14:39:55 +0800"], "statuses": {"200": 4478, "302": 485, "404": 26, "499": 11}, "top_path_patterns": [["/index.php?s=user-center-flushinfo", 488], ["/user-center-login.html", 481], ["/index.php?s=User-Comm-getcomm-id-0", 178], ["/favicon.ico", 127], ["/tutorial/56887.html", 26], ["/index.php?s=hits-show-id-56887-type-insert-sid-news", 24], ["/app_games/indexN.html", 21], ["/", 8], ["/index.php?s=plus-post-news", 6], ["/tutorial/56874.html", 6], ["/index.php?s=hits-show-id-56874-type-insert-sid-news", 6], ["/soft/", 5], ["/index.php?s=User-Comm-getcomm-id-86982", 4], ["/downgame/indexN.html", 3], ["/index.php?s=hits-show-id-87565-type-insert-sid-down", 3]], "top_user_agents": [["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36", 1376], ["Mozilla/5.0 (compatible; AhrefsBot/7.0; +http://ahrefs.com/robot/)", 725], ["Mozilla/5.0 (Linux; Android 5.0) AppleWebKit/537.36 (KHTML, like Gecko) Mobile Safari/537.36 (compatible; Bytespider; https://zhanzhang.toutiao.com/)", 520], ["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", 513], ["Mozilla/5.0 (compatible; SemrushBot/7~bl; +http://www.semrush.com/bot.html)", 218], ["Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0", 91], ["Mozilla/5.0 (compatible; DataForSeoBot/1.0; +https://dataforseo.com/dataforseo-bot)", 88], ["Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm) Chrome/116.0.1938.76 Safari/537.36", 84]], "unique_sources": 2116, "max_requests_one_source": 88, "network_Mbps": {"eth0": {"rx": 5.67, "tx": 6.0}}, "cpu": {"user": 67.0, "system": 19.0, "idle": 13.0, "iowait": 1.0}}
|
||||
Reference in New Issue
Block a user