auto-sync: 2026-05-11 22:10:01

This commit is contained in:
Stream
2026-05-11 22:10:02 +03:00
parent 56f9a597b9
commit 406ed02e16
8 changed files with 307 additions and 14 deletions

View File

@@ -19,6 +19,7 @@ from typing import List
from functools import lru_cache from functools import lru_cache
from fastapi import FastAPI, HTTPException, Response from fastapi import FastAPI, HTTPException, Response
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel from pydantic import BaseModel
@@ -1218,6 +1219,31 @@ async def health():
} }
# ─── Terrain tiles ───────────────────────────────────────────────────────────
TERRAIN_DIR = os.environ.get(
"TERRAIN_DIR",
os.path.join(os.path.dirname(__file__), "../data/terrain"),
)
@app.get("/terrain/{layer}/{z}/{x}/{y}.png")
async def terrain_tile(layer: str, z: int, x: int, y: int):
"""Отдаёт растровые тайлы рельефа (hypso/hillshade)"""
if layer not in ("hypso", "hillshade"):
raise HTTPException(404, "Unknown layer")
tile_path = os.path.join(TERRAIN_DIR, layer, str(z), str(x), f"{y}.png")
if not os.path.exists(tile_path):
raise HTTPException(404, "Tile not found")
return FileResponse(
tile_path,
media_type="image/png",
headers={
"Cache-Control": "public, max-age=31536000, immutable",
"Access-Control-Allow-Origin": "*",
}
)
# ─── Static files ───────────────────────────────────────────────────────────── # ─── Static files ─────────────────────────────────────────────────────────────
if os.path.exists(STATIC_DIR): if os.path.exists(STATIC_DIR):

View File

@@ -0,0 +1,27 @@
const { Client } = require('ssh2');
const conn = new Client();
function exec(conn, cmd) {
return new Promise((resolve, reject) => {
conn.exec(cmd, (err, stream) => {
if (err) return reject(err);
let out = '', e = '';
stream.on('data', d => out += d);
stream.stderr.on('data', d => e += d);
stream.on('close', () => resolve({ out: out.trim(), err: e.trim() }));
});
});
}
conn.on('ready', async () => {
const r1 = await exec(conn, 'grep -n "terrain" /home/slin/enduro-trails/prototype/static/app.js | head -20');
console.log('app.js terrain refs:', r1.out || '(none)');
const r2 = await exec(conn, 'grep -n "terrain" /home/slin/enduro-trails/prototype/static/index.html | head -20');
console.log('index.html terrain refs:', r2.out || '(none)');
const r3 = await exec(conn, 'grep -n "terrain" /home/slin/enduro-trails/prototype/static/app.css | head -10');
console.log('app.css terrain refs:', r3.out || '(none)');
conn.end();
});
conn.on('error', err => { console.error('error:', err.message); process.exit(1); });
conn.connect({ host: '82.22.50.71', username: 'slin', password: 'motoZ@yaz2010', readyTimeout: 15000 });

View File

@@ -0,0 +1,37 @@
const { Client } = require('ssh2');
const conn = new Client();
function exec(conn, cmd) {
return new Promise((resolve, reject) => {
conn.exec(cmd, (err, stream) => {
if (err) return reject(err);
let out = '', e = '';
stream.on('data', d => out += d);
stream.stderr.on('data', d => e += d);
stream.on('close', () => resolve({ out: out.trim(), err: e.trim() }));
});
});
}
conn.on('ready', async () => {
// Реальный тайл через nginx (enduro/ → FastAPI, FastAPI отдаёт /terrain/)
const r1 = await exec(conn, 'curl -o /dev/null -s -w "%{http_code}" "https://openclaw.mva154.duckdns.org/enduro/terrain/hypso/8/161/179.png"');
console.log('nginx /enduro/terrain/hypso/8/161/179.png:', r1.out);
// Прямо на FastAPI
const r2 = await exec(conn, 'curl -o /dev/null -s -w "%{http_code}" "http://localhost:5558/terrain/hypso/8/161/179.png"');
console.log('direct FastAPI /terrain/hypso/8/161/179.png:', r2.out);
// Проверить hillshade статус
const r3 = await exec(conn, 'find /home/slin/enduro-trails/data/terrain/hillshade -name "*.png" 2>/dev/null | wc -l');
console.log('hillshade PNG count:', r3.out);
const r4 = await exec(conn, 'pgrep -c gdal2tiles 2>/dev/null || echo 0');
console.log('gdal2tiles procs:', r4.out);
// Проверить зумы hillshade
const r5 = await exec(conn, 'ls /home/slin/enduro-trails/data/terrain/hillshade/ 2>/dev/null | grep -v xml | sort -n | tr "\\n" " "');
console.log('hillshade zooms:', r5.out || '(none)');
conn.end();
});
conn.on('error', err => { console.error('error:', err.message); process.exit(1); });
conn.connect({ host: '82.22.50.71', username: 'slin', password: 'motoZ@yaz2010', readyTimeout: 15000 });

View File

@@ -0,0 +1,36 @@
const { Client } = require('ssh2');
const conn = new Client();
function exec(conn, cmd) {
return new Promise((resolve, reject) => {
conn.exec(cmd, (err, stream) => {
if (err) return reject(err);
let out = '', e = '';
stream.on('data', d => out += d);
stream.stderr.on('data', d => e += d);
stream.on('close', () => resolve({ out: out.trim(), err: e.trim() }));
});
});
}
conn.on('ready', async () => {
const r1 = await exec(conn, 'ls /home/slin/enduro-trails/data/terrain/hypso/8/ | head -5');
console.log('hypso/8/ dirs:', r1.out || r1.err);
const r2 = await exec(conn, 'ls /home/slin/enduro-trails/data/terrain/hypso/8/75/ 2>/dev/null | head -5');
console.log('hypso/8/75/ files:', r2.out || r2.err);
const r3 = await exec(conn, 'docker exec prototype-enduro-trails-1 ls /app/data/ 2>/dev/null');
console.log('container /app/data:', r3.out || r3.err || 'empty');
const r4 = await exec(conn, 'docker inspect prototype-enduro-trails-1 --format "{{range .Mounts}}{{.Source}}->{{.Destination}} {{end}}"');
console.log('mounts:', r4.out || r4.err);
const r5 = await exec(conn, 'docker exec prototype-enduro-trails-1 python3 -c "import os; p=os.path.join(os.path.dirname(\'/app/app.py\'), \'../data/terrain\'); print(p); print(os.path.exists(p))"');
console.log('TERRAIN_DIR:', r5.out || r5.err);
const r6 = await exec(conn, 'curl -s http://localhost:5558/api/health');
console.log('health:', r6.out || r6.err);
conn.end();
});
conn.on('error', err => { console.error('error:', err.message); process.exit(1); });
conn.connect({ host: '82.22.50.71', username: 'slin', password: 'motoZ@yaz2010', readyTimeout: 15000 });

View File

@@ -0,0 +1,47 @@
const { Client } = require('ssh2');
const conn = new Client();
function exec(conn, cmd) {
return new Promise((resolve, reject) => {
conn.exec(cmd, (err, stream) => {
if (err) return reject(err);
let out = '', e = '';
stream.on('data', d => out += d);
stream.stderr.on('data', d => e += d);
stream.on('close', () => resolve({ out: out.trim(), err: e.trim() }));
});
});
}
conn.on('ready', async () => {
// Проверить что реально есть в hypso/8/75/
const r1 = await exec(conn, 'ls /home/slin/enduro-trails/data/terrain/hypso/8/75/ 2>/dev/null | head -10');
console.log('hypso/8/75/ files:', r1.out || '(empty)');
// Найти любой существующий тайл z8
const r2 = await exec(conn, 'find /home/slin/enduro-trails/data/terrain/hypso/8/ -name "*.png" | head -5');
console.log('sample z8 tiles:', r2.out || '(none)');
// Проверить структуру — TMS vs XYZ (y может быть перевёрнутым)
const r3 = await exec(conn, 'ls /home/slin/enduro-trails/data/terrain/hypso/8/ | sort -n | head -10');
console.log('z8 x-dirs:', r3.out);
// Попробовать curl на реальный тайл
const r4 = await exec(conn, 'find /home/slin/enduro-trails/data/terrain/hypso/8/ -name "*.png" | head -1');
const tile = r4.out;
console.log('first tile path:', tile);
if (tile) {
// Извлечь z/x/y из пути
const parts = tile.split('/');
const y = parts[parts.length-1].replace('.png','');
const x = parts[parts.length-2];
const z = parts[parts.length-3];
const url = `http://localhost:5558/terrain/hypso/${z}/${x}/${y}.png`;
console.log('test URL:', url);
const r5 = await exec(conn, `curl -o /dev/null -s -w "%{http_code}" "${url}"`);
console.log('HTTP:', r5.out);
}
conn.end();
});
conn.on('error', err => { console.error('error:', err.message); process.exit(1); });
conn.connect({ host: '82.22.50.71', username: 'slin', password: 'motoZ@yaz2010', readyTimeout: 15000 });

View File

@@ -0,0 +1,80 @@
const { Client } = require('ssh2');
const fs = require('fs');
const path = require('path');
const conn = new Client();
const config = {
host: '82.22.50.71',
username: 'slin',
password: 'motoZ@yaz2010',
readyTimeout: 30000
};
function exec(conn, cmd) {
return new Promise((resolve, reject) => {
conn.exec(cmd, (err, stream) => {
if (err) return reject(err);
let out = '', e = '';
stream.on('data', d => out += d);
stream.stderr.on('data', d => e += d);
stream.on('close', () => resolve({ out: out.trim(), err: e.trim() }));
});
});
}
function sftp_put(sftp, localPath, remotePath) {
return new Promise((resolve, reject) => {
sftp.fastPut(localPath, remotePath, (err) => {
if (err) reject(err); else resolve();
});
});
}
conn.on('ready', async () => {
console.log('✅ Connected');
conn.sftp(async (err, sftp) => {
if (err) { console.error('SFTP error:', err); conn.end(); return; }
// 1. Загрузить app.py
const localApp = path.join(__dirname, 'app.py');
const remoteApp = '/home/slin/enduro-trails/prototype/app.py';
console.log('📤 Uploading app.py...');
await sftp_put(sftp, localApp, remoteApp);
console.log('✅ app.py uploaded');
// 2. docker cp app.py в контейнер
const cp = await exec(conn, 'docker cp /home/slin/enduro-trails/prototype/app.py prototype-enduro-trails-1:/app/app.py');
console.log('docker cp app.py:', cp.out || cp.err || 'ok');
// 3. Рестарт контейнера
console.log('🔄 Restarting container...');
const restart = await exec(conn, 'docker restart prototype-enduro-trails-1');
console.log('restart:', restart.out || restart.err);
// 4. Подождать 8 сек
await new Promise(r => setTimeout(r, 8000));
// 5. docker cp статики после рестарта
const cpJs = await exec(conn, 'docker cp /home/slin/enduro-trails/prototype/static/app.js prototype-enduro-trails-1:/app/static/app.js');
console.log('docker cp app.js:', cpJs.out || cpJs.err || 'ok');
const cpCss = await exec(conn, 'docker cp /home/slin/enduro-trails/prototype/static/app.css prototype-enduro-trails-1:/app/static/app.css');
console.log('docker cp app.css:', cpCss.out || cpCss.err || 'ok');
const cpHtml = await exec(conn, 'docker cp /home/slin/enduro-trails/prototype/static/index.html prototype-enduro-trails-1:/app/static/index.html');
console.log('docker cp index.html:', cpHtml.out || cpHtml.err || 'ok');
// 6. Проверить terrain endpoint
await new Promise(r => setTimeout(r, 3000));
const http = await exec(conn, 'curl -o /dev/null -s -w "%{http_code}" "https://openclaw.mva154.duckdns.org/enduro/terrain/hypso/8/75/42.png"');
console.log('\n🌐 HTTP terrain hypso z8:', http.out);
const http2 = await exec(conn, 'curl -o /dev/null -s -w "%{http_code}" "http://localhost:5558/terrain/hypso/8/75/42.png"');
console.log('🌐 HTTP direct FastAPI:', http2.out);
sftp.end();
conn.end();
});
});
conn.on('error', err => { console.error('❌', err.message); process.exit(1); });
conn.connect(config);

View File

@@ -1,35 +1,42 @@
const { Client } = require('ssh2'); const { Client } = require('ssh2');
const conn = new Client(); const conn = new Client();
function exec(conn, cmd) { function exec(conn, cmd) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
conn.exec(cmd, (err, stream) => { conn.exec(cmd, (err, stream) => {
if (err) return reject(err); if (err) return reject(err);
let out = ''; let out = '', err2 = '';
stream.on('data', d => out += d); stream.on('data', d => out += d);
stream.stderr.on('data', d => out += d); stream.stderr.on('data', d => err2 += d);
stream.on('close', () => resolve(out.trim())); stream.on('close', () => resolve({ out: out.trim(), err: err2.trim() }));
}); });
}); });
} }
conn.on('ready', async () => { conn.on('ready', async () => {
console.log('✅ Connected\n'); console.log('✅ Connected\n');
// Проверить права на /home/slin // Проверить sudoers файл напрямую
const homePerm = await exec(conn, 'stat -c "%a %U %G" /home/slin'); const sudoers = await exec(conn, 'cat /etc/sudoers.d/* 2>/dev/null || echo "нет доступа"');
console.log('home/slin perms:', homePerm); console.log('sudoers.d:', sudoers.out || sudoers.err);
// Дать www-data доступ к /home/slin (execute bit для others) // Проверить группы slin
const chmod1 = await exec(conn, 'echo motoZ@yaz2010 | sudo -S chmod o+x /home/slin'); const groups = await exec(conn, 'id slin');
console.log('chmod home/slin:', chmod1 || 'ok'); console.log('groups:', groups.out);
// Дать доступ к enduro-trails/data // Проверить есть ли у slin права на nginx конфиг
const chmod2 = await exec(conn, 'echo motoZ@yaz2010 | sudo -S chmod -R o+rX /home/slin/enduro-trails/data/terrain/'); const nginxConf = await exec(conn, 'ls -la /etc/nginx/sites-enabled/ && ls -la /etc/nginx/nginx.conf');
console.log('chmod terrain:', chmod2 || 'ok'); console.log('nginx files:', nginxConf.out);
// Проверить снова // Попробовать через script -c
const r = await exec(conn, 'script -q -c "echo motoZ@yaz2010 | sudo -S chmod o+x /home/slin" /dev/null 2>&1');
console.log('script sudo:', r.out || r.err);
// HTTP check
const http = await exec(conn, 'curl -o /dev/null -s -w "%{http_code}" "https://openclaw.mva154.duckdns.org/enduro/terrain/hypso/8/75/42.png"'); const http = await exec(conn, 'curl -o /dev/null -s -w "%{http_code}" "https://openclaw.mva154.duckdns.org/enduro/terrain/hypso/8/75/42.png"');
console.log('HTTP после фикса:', http); console.log('HTTP:', http.out);
conn.end(); conn.end();
}); });
conn.on('error', err => { console.error('❌', err.message); process.exit(1); });
conn.connect({ host: '82.22.50.71', username: 'slin', password: 'motoZ@yaz2010', readyTimeout: 15000 }); conn.connect({ host: '82.22.50.71', username: 'slin', password: 'motoZ@yaz2010', readyTimeout: 15000 });

View File

@@ -0,0 +1,33 @@
const { Client } = require('ssh2');
const fs = require('fs');
const conn = new Client();
function exec(conn, cmd) {
return new Promise((resolve, reject) => {
conn.exec(cmd, (err, stream) => {
if (err) return reject(err);
let out = '', err2 = '';
stream.on('data', d => out += d);
stream.stderr.on('data', d => err2 += d);
stream.on('close', () => resolve({ out: out.trim(), err: err2.trim() }));
});
});
}
conn.on('ready', async () => {
console.log('✅ Connected\n');
// Читаем текущий nginx конфиг для openclaw
const conf = await exec(conn, 'cat /etc/nginx/sites-enabled/openclaw.mva154.duckdns.org');
console.log('=== CURRENT NGINX CONF ===');
console.log(conf.out);
// Читаем app.py чтобы понять структуру
const apppy = await exec(conn, 'head -50 /home/slin/enduro-trails/prototype/app.py');
console.log('\n=== APP.PY HEAD ===');
console.log(apppy.out);
conn.end();
});
conn.on('error', err => { console.error('❌', err.message); process.exit(1); });
conn.connect({ host: '82.22.50.71', username: 'slin', password: 'motoZ@yaz2010', readyTimeout: 15000 });