avk013.blogspot.com - другой мой блог "C# and etc"

четверг, 4 июня 2026 г.

watch dog на Arduino Nano + ENC28J60

 абсолютно неактуальная плата, которая лежала 10 лет, ввиду обнищания было решено приспособить для перезагрузки удаленного коммутатора, в виду его случайных проблем по питанию. Адаптировано с КЛОДОМ. >>>ссылка<<<
===

// PING WATCHDOG v4.5

//  Arduino Nano + ENC28J60 + реле
// ============================================================
//
//  НАЗНАЧЕНИЕ:
//    Периодически проверяет доступность хоста (UDP DNS-запрос).
//    При потерях >= порога — отключает реле на 30 сек, затем
//    ГАРАНТИРОВАННО включает обратно (два независимых механизма).
//    Веб: статус открыт, управление по паролю.
//    Пароль — в EEPROM, меняется через веб без перепрошивки.
//    Сброс настроек — кнопкой при старте.
//    WDT 8 сек — защита от зависания.
//
//  ПОЧЕМУ UDP А НЕ TCP:
//    EthernetClient.connect() в UIPEthernet игнорирует setTimeout()
//    и блокирует loop() до 30 сек — контроллер зависает.
//    UDP неблокирующий: отправили → ждём в loop() → таймаут.
//    Фильтрация мусора: проверяем Transaction ID в ответе (0xABCD).
//    ICMP unreachable от роутера имеет другой формат → отбрасывается.
//
//  СХЕМА:
//    ENC28J60 → Nano:  VCC→3.3V  GND→GND
//      SCK→D13  SI→D11  SO→D12  CS→D10
//
//    РЕЛЕ (активный LOW модуль):
//      D4 → IN прямой    (HIGH = нагрузка ВКЛ)
//      D5 → IN инверсный (LOW  = нагрузка ВКЛ, всегда !D4)
//      Нагрузка на NO контакте реле.
//
//    КНОПКА СБРОСА:
//      D7 → кнопка → GND (INPUT_PULLUP)
//      Зажать при подаче питания, держать 3 сек →
//      сброс IP / интервала / пароля к заводским.
//      LED мигнёт 5 раз быстро = подтверждение.
//
//    HEARTBEAT LED (внешний):
//      D8 → 220 Ом → LED → GND
//      1 Гц  = норма
//      5 Гц  = реле отключено (идёт перезагрузка нагрузки)
//      тихо  = контроллер завис
//
//  ПЕРВЫЙ ЗАПУСК:
//    1. Отключить ENC28J60, прошить, подключить обратно.
//    2. http://192.168.3.15 — статус (без пароля).
//    3. Пароль по умолчанию: 1234
//
//  СБРОС (забыт пароль):
//    Зажать D7, включить питание, держать 3 сек.
//    Пароль→1234, IP→192.168.3.254, интервал→2 мин.
//
//  ЗАВИСИМОСТИ:
//    UIPEthernet — https://github.com/UIPEthernet/UIPEthernet
// ============================================================

#include <UIPEthernet.h>
#include <EEPROM.h>
#include <avr/wdt.h>

// --- Пины ---
#define RLY1  4
#define RLY2  5
#define BTN   7
#define LED   8

// --- Параметры проверки ---
#define P_CNT    10        // пакетов в серии
#define P_STEP  700UL      // интервал между отправками, мс
#define P_TOUT  600UL      // таймаут ожидания ответа, мс
                           // P_STEP > P_TOUT — новый пакет только
                           // после завершения ожидания предыдущего
#define LOSS_T   50        // % потерь для срабатывания

// Transaction ID нашего DNS-запроса — по нему фильтруем ответ
#define DNS_TID_HI  0xAB
#define DNS_TID_LO  0xCD

// --- Реле ---
#define REBOOT  30000UL
#define SAFETY   5000UL

// --- Веб ---
#define WEB_T    1500UL

// --- EEPROM ---
#define EE_MAGIC  0xA9
#define EE_ADR    0
#define PW_LEN   13        // макс 12 символов + \0

struct Cfg {
  byte    mag;
  byte    ip[4];
  uint8_t intv;
  char    pw[PW_LEN];
} cfg;

// ============================================================
//  Сеть
// ============================================================
byte           mac[] = { 0xDE,0xAD,0xBE,0xEF,0xFE,0xED };
IPAddress      myIp(192, 168, 3, 15);
EthernetServer srv(80);
EthernetUDP    udp;

// DNS-запрос: Transaction ID=0xABCD, запрос A-записи "."
static const byte DNSQ[] PROGMEM = {
  0xAB,0xCD, 0x01,0x00, 0x00,0x01, 0x00,0x00,
  0x00,0x00, 0x00,0x00, 0x00, 0x00,0x01, 0x00,0x01
};

// ============================================================
//  Состояние
// ============================================================
bool          rlyOff = false;
unsigned long rlyOnT = 0;
unsigned long safeT  = 0;

// Конечный автомат:
//   IDLE: ждём времени серии
//   SEND: отправляем UDP, переходим в WAIT
//   WAIT: ждём ответ (неблокирующий), таймаут → SEND следующего
//   DONE: итог серии
enum St : byte { IDLE, SEND, WAIT, DONE } pSt = IDLE;
unsigned long lastSer = 0;
unsigned long lastSt  = 0;
unsigned long sentT   = 0;
byte pSent = 0, pLost = 0, loss = 0;

unsigned long hbT  = 0;
bool          hbSt = false;

// 0=RDY 1=PRB 2=OK 3=FAIL 4=OFF30s 5=RST 6=SAV 7=BADPW
byte stat = 0;
const char m0[] PROGMEM = "RDY";
const char m1[] PROGMEM = "PRB";
const char m2[] PROGMEM = "OK";
const char m3[] PROGMEM = "FAIL";
const char m4[] PROGMEM = "OFF30s";
const char m5[] PROGMEM = "RST";
const char m6[] PROGMEM = "SAV";
const char m7[] PROGMEM = "BADPW";
const char* const MSGS[] PROGMEM = {m0,m1,m2,m3,m4,m5,m6,m7};

// ============================================================
//  Утилиты
// ============================================================
void getM(byte i, char* b) {
  strncpy_P(b, (char*)pgm_read_word(&MSGS[i]), 11);
  b[11] = '\0';
}

bool gP(const char* req, const char* key, char* buf, byte sz) {
  const char* p = strstr(req, key);
  if (!p) return false;
  p += strlen(key);
  byte i = 0;
  while (*p && *p != '&' && *p != ' ' && i < sz-1) buf[i++] = *p++;
  buf[i] = '\0';
  return i > 0;
}

bool pIp(const char* s, byte* o) {
  byte v=0, d=0, n=0;
  for (byte i=0; ; i++) {
    char c = s[i];
    if (c>='0'&&c<='9') { v=v*10+(c-'0'); n++; }
    else if ((c=='.'||c=='\0')&&n) {
      if (v>255) return false;
      o[d++]=v; v=0; n=0;
      if (!c) break;
    } else return false;
    if (d>4) return false;
  }
  return d==4;
}

void prIp(EthernetClient& c) {
  c.print(cfg.ip[0]); c.print('.');
  c.print(cfg.ip[1]); c.print('.');
  c.print(cfg.ip[2]); c.print('.');
  c.print(cfg.ip[3]);
}

void prP(EthernetClient& c, const char* p) {
  char ch; while ((ch=pgm_read_byte(p++))) c.write(ch);
}

// ============================================================
//  Реле
// ============================================================
void setR(bool on) {
  digitalWrite(RLY1, on ? HIGH : LOW);
  digitalWrite(RLY2, on ? LOW  : HIGH);
  rlyOff = !on;
  if (on) { rlyOnT=0; safeT=0; }
}

// ============================================================
//  EEPROM
// ============================================================
void defCfg() {
  cfg.mag   = EE_MAGIC;
  cfg.ip[0] = 192; cfg.ip[1] = 168;
  cfg.ip[2] = 3;   cfg.ip[3] = 254;
  cfg.intv  = 2;
  strncpy(cfg.pw, "1234", PW_LEN);
}

void loadC() {
  EEPROM.get(EE_ADR, cfg);
  if (cfg.mag != EE_MAGIC) { defCfg(); EEPROM.put(EE_ADR, cfg); }
}

void rstC() {
  defCfg(); EEPROM.put(EE_ADR, cfg);
  for (byte i=0; i<10; i++) { digitalWrite(LED, i&1); delay(80); }
}

// ============================================================
//  Heartbeat
// ============================================================
void hbUp(unsigned long now) {
  unsigned long p = rlyOff ? 100UL : 500UL;
  if (now-hbT >= p) { hbT=now; hbSt=!hbSt; digitalWrite(LED,hbSt); }
}

// ============================================================
//  UDP-зонд: отправить DNS-запрос
//  Неблокирующий — только отправка, ответ читаем в WAIT
// ============================================================
bool sendP() {
  static uint16_t prt = 50000;
  if (++prt < 50001) prt = 50001;
  udp.stop();
  if (!udp.begin(prt)) return false;
  IPAddress t(cfg.ip[0],cfg.ip[1],cfg.ip[2],cfg.ip[3]);
  udp.beginPacket(t, 53);
  for (byte i=0; i<17; i++) udp.write(pgm_read_byte(&DNSQ[i]));
  return udp.endPacket()==1;
}

// Проверить ответ: валидный DNS-ответ с нашим Transaction ID?
// Возвращает: 1=наш ответ, -1=чужой пакет/мусор, 0=нет пакета
int8_t checkReply() {
  int len = udp.parsePacket();
  if (len <= 0) return 0;          // нет пакета
  if (len < 4)  { udp.flush(); return -1; }  // слишком короткий
  byte b0 = udp.read();
  byte b1 = udp.read();
  udp.flush();
  // Проверяем Transaction ID — первые 2 байта DNS-ответа
  if (b0==DNS_TID_HI && b1==DNS_TID_LO) return 1;  // наш ответ
  return -1;  // чужой пакет (ICMP unreachable и пр.)
}

// ============================================================
//  Перезагрузка нагрузки
// ============================================================
void doReboot() {
  if (rlyOff) return;
  pSt   = IDLE;
  setR(false);
  rlyOnT = millis() + REBOOT;
  if (!rlyOnT) rlyOnT = 1;
  safeT  = millis();
  stat   = 4;
}

// ============================================================
//  HTML
// ============================================================
const char H1[] PROGMEM =
  "HTTP/1.1 200 OK\r\nContent-Type:text/html;charset=utf-8\r\n"
  "Connection:close\r\n\r\n"
  "<!DOCTYPE html><html><head>"
  "<meta charset='utf-8'>"
  "<meta name='viewport' content='width=device-width,initial-scale=1'>"
  "<title>WD</title></head><body><h3>WD-gofra</h3>"
  "RLY:";
const char H2[] PROGMEM = "<br>IP:";
const char H3[] PROGMEM = "<br>INT:";
const char H4[] PROGMEM = "m<br>LOSS:";
const char H5[] PROGMEM = "%<br>ST:";

const char HL[] PROGMEM =
  "<hr><form action='/' method='get'>"
  "<input type='password' name='p' maxlength='12' placeholder='Password'>"
  " <input type='submit' value='Login'>"
  "</form></body></html>";

const char HA1[] PROGMEM = "<hr><a href='/reboot?p=";
const char HA2[] PROGMEM = "'>[REBOOT 30s]</a><br><br>"
  "<form action='/set' method='get'>"
  "<input type='hidden' name='p' value='";
const char HA3[] PROGMEM = "'>"
  "IP:<input type='text' name='ip' value='";
const char HA4[] PROGMEM = "' maxlength='15' required><br>"
  "INT(m):<input type='number' name='int' value='";
const char HA5[] PROGMEM = "' min='1' max='60' required><br>"
  "NewPW:<input type='password' name='np' maxlength='12'><br>"
  "<input type='submit' value='SAVE'>"
  "</form></body></html>";

void sendPg(EthernetClient& c, bool adm, const char* pw) {
  prP(c,H1);
  if (rlyOff) {
    unsigned long rem=0, now=millis();
    if (rlyOnT>now) rem=(rlyOnT-now)/1000;
    c.print(F("OFF(")); c.print(rem); c.print(F("s)"));
  } else { c.print(F("ON")); }
  prP(c,H2); prIp(c);
  prP(c,H3); c.print(cfg.intv);
  prP(c,H4); c.print(loss);
  prP(c,H5);
  char b[12]; getM(stat,b); c.print(b);
  if (!adm) {
    prP(c,HL);
  } else {
    prP(c,HA1); c.print(pw);
    prP(c,HA2); c.print(pw);
    prP(c,HA3); prIp(c);
    prP(c,HA4); c.print(cfg.intv);
    prP(c,HA5);
  }
}

// ============================================================
//  Веб-обработчик
// ============================================================
void hWeb() {
  EthernetClient cl = srv.available();
  if (!cl) return;

  char req[100]; byte rLen=0;
  unsigned long t=millis();
  while (cl.connected() && millis()-t<WEB_T) {
    if (cl.available()) {
      char ch=cl.read();
      if (ch=='\n') break;
      if (ch!='\r' && rLen<sizeof(req)-1) req[rLen++]=ch;
    }
  }
  req[rLen]='\0';

  { char pv=0; t=millis();
    while (cl.connected() && millis()-t<WEB_T) {
      if (cl.available()) {
        char ch=cl.read();
        if (ch=='\n'&&pv=='\n') break;
        pv=(ch=='\r')?pv:ch;
      }
    }
  }

  char pw[PW_LEN]; pw[0]='\0';
  gP(req,"p=",pw,sizeof(pw));
  bool adm = pw[0] && strcmp(pw,cfg.pw)==0;

  if (adm) {
    if (strstr(req,"/reboot")) {
      doReboot();
    } else if (strstr(req,"/set")) {
      bool ok=false;
      char ib[16];
      if (gP(req,"ip=",ib,sizeof(ib))) {
        byte tmp[4];
        if (pIp(ib,tmp)) { memcpy(cfg.ip,tmp,4); ok=true; }
      }
      char nb[4];
      if (gP(req,"int=",nb,sizeof(nb))) {
        int v=atoi(nb); if (v>=1&&v<=60) { cfg.intv=v; ok=true; }
      }
      char np[PW_LEN];
      if (gP(req,"np=",np,sizeof(np)) && np[0]) {
        strncpy(cfg.pw,np,PW_LEN-1); cfg.pw[PW_LEN-1]='\0'; ok=true;
      }
      if (ok) { EEPROM.put(EE_ADR,cfg); stat=6; }
    }
  } else if (pw[0]) { stat=7; }

  sendPg(cl,adm,pw);
  cl.stop();
}

// ============================================================
//  setup()
// ============================================================
void setup() {
  pinMode(RLY1, OUTPUT);
  pinMode(RLY2, OUTPUT);
  pinMode(BTN,  INPUT_PULLUP);
  pinMode(LED,  OUTPUT);
  setR(true);

  if (digitalRead(BTN)==LOW) {
    unsigned long ts=millis();
    while (digitalRead(BTN)==LOW) {
      if (millis()-ts > 3000UL) { rstC(); break; }
      digitalWrite(LED, (millis()/200)&1);
    }
  }

  loadC();
  Ethernet.begin(mac, myIp);
  srv.begin();
  wdt_enable(WDTO_8S);
}

// ============================================================
//  loop()
// ============================================================
void loop() {
  wdt_reset();
  unsigned long now = millis();

  hbUp(now);

  // Гарантированное включение реле (два механизма)
  if (rlyOff) {
    if ((long)(now-rlyOnT) >= 0) {
      setR(true); lastSer=now; stat=5;
    } else if (now-safeT >= REBOOT+SAFETY) {
      setR(true); lastSer=now; stat=5;
    }
  }

  // Конечный автомат проверки — полностью неблокирующий
  if (!rlyOff) {
    unsigned long iv = (unsigned long)cfg.intv * 60000UL;
    switch (pSt) {

      case IDLE:
        if (now-lastSer >= iv) {
          pSent=pLost=0; pSt=SEND; lastSt=now; stat=1;
        }
        break;

      case SEND:
        // Ждём P_STEP после предыдущего пакета, затем отправляем
        if (now-lastSt >= P_STEP) {
          lastSt=now;
          if (sendP()) {
            sentT=now;
            pSt=WAIT;
          } else {
            // Не смогли отправить — считаем потерей
            pLost++; pSent++;
            if (pSent>=P_CNT) pSt=DONE;
          }
        }
        break;

      case WAIT:
        // Каждую итерацию loop() проверяем — пришёл ли ответ
        {
          int8_t r = checkReply();
          if (r == 1) {
            // Валидный DNS-ответ с нашим TID — успех
            pSent++;
            pSt = (pSent>=P_CNT) ? DONE : SEND;
            lastSt=now;
          } else if (r == -1) {
            // Чужой пакет (ICMP мусор) — игнорируем, ждём дальше
            // но не сбрасываем таймаут
          } else if (now-sentT >= P_TOUT) {
            // Таймаут — потеря
            pLost++; pSent++;
            pSt = (pSent>=P_CNT) ? DONE : SEND;
            lastSt=now;
          }
        }
        break;

      case DONE:
        loss = (pLost*100)/P_CNT;
        lastSer=now; pSt=IDLE;
        if (loss>=LOSS_T) { stat=3; doReboot(); }
        else stat=2;
        break;
    }
  }

  hWeb();
  Ethernet.maintain();
}
===

среда, 3 июня 2026 г.

экспорт данных из АСКУЭ ПЧЕЛА 5.6 в CSV

первый шаг1 к дружелюбной обработке данных их очень немолодой программы АСКУЕ ПЧЕЛА 5.6 с хранением данных на локальном сервере FireBird-1.5.1.4481-Win32 в режиме суперсервера, в нашем случае при взаимодействии с модулем ИРКА.
начало положено https://github.com/avk013/pchela2web
помощь от нескольких ИИ получена, 
отладка выполнена для нашего конкретного случая с одним портом.
поражаюсь но достаточно bat файла,
и вообще все ЭТО попахивает НЕКРО, НО раз надо значит надо!!!
================

@echo off
chcp 1251 > nul

:: === НАСТРОЙКИ ПУТЕЙ ===
set FB_BIN=C:\Program Files (x86)\firebird\bin
set EXPORT_DIR=C:\4export
set OUTPUT_FILE=%EXPORT_DIR%\fb_last_month.csv

:: === НАСТРОЙКИ ПОДКЛЮЧЕНИЯ ===
set DB_USER=SYSDBA
set DB_PASS=masterkey

:: Путь к живой базе
set LIVE_DB=127.0.0.1:"C:\Program Files\Пчела5.6\Base\bars.fdb"

:: === ГЕНЕРАЦИЯ УНИКАЛЬНОГО ИМЕНИ ФАЙЛА ===
set TIMESTAMP=%TIME::=%
set TIMESTAMP=%TIMESTAMP: =0%
set TIMESTAMP=%DATE%%TIMESTAMP:~0,4%

set BACKUP_FBK=%EXPORT_DIR%\temp_bk%TIMESTAMP%.fbk
set COPY_FDB=%EXPORT_DIR%\temp_db_%TIMESTAMP%.fdb
set COPY_FDB2=127.0.0.1:%COPY_FDB%

:: =========================================================================
if not exist "%EXPORT_DIR%" mkdir "%EXPORT_DIR%"

echo 1. Создание снимка живой базы...
"%FB_BIN%\gbak.exe" -b -user %DB_USER% -password %DB_PASS% %LIVE_DB% "%BACKUP_FBK%"
if errorlevel 1 goto :err_backup
timeout /t 10

echo 2. Разворачивание временной копии...
"%FB_BIN%\gbak.exe" -c -user %DB_USER% -password %DB_PASS% "%BACKUP_FBK%" %COPY_FDB2%
if errorlevel 1 goto :err_restore

if exist "%BACKUP_FBK%" del /f /q "%BACKUP_FBK%"

echo 3. Экспорт данных в CSV...
set SQL_QUERY="SELECT * FROM ARCH_HOURS"
fbexport.exe -Sc -H 127.0.0.1 -U %DB_USER% -P %DB_PASS% -D %COPY_FDB% -Q %SQL_QUERY% -B ";" -F "%OUTPUT_FILE%" -A WIN1251
if errorlevel 1 goto :err_export

echo [УСПЕХ] Экспорт завершен!
goto :end

:err_backup
echo [ОШИБКА] Не удалось сделать снимок базы.
pause
goto :end

:err_restore
echo [ОШИБКА] Не удалось развернуть снимок базы.
pause
goto :end

:err_export
echo [ОШИБКА] Ошибка при выгрузке данных.
pause

:end
echo 5. Очистка временных файлов...
if exist "%BACKUP_FBK%" del /f /q "%BACKUP_FBK%" 2>nul
if exist "%COPY_FDB%"   del /f /q "%COPY_FDB%"   2>nul
for %%f in ("%EXPORT_DIR%\temp_db_*.fdb") do del /f /q "%%f" 2>nul
for %%f in ("%EXPORT_DIR%\temp_bk*.fbk") do del /f /q "%%f" 2>nul

echo Работа скрипта завершена.
pause
================
условно версия >>>B1<<< для проекта в будущем pchela2web
и сам экспорт в LiteSQL и визуализация php >>>web<<<

вторник, 12 мая 2026 г.

wsdd. Для того чтобы немолодой samba был виден в относительно актуально Windows 10/11

Сетевое обнаружение (wsdd)
Для того чтобы Windows 10/11 видела сервер в «Сетевом окружении»:

Создайте файл: nano /usr/bin/wsdd (вставьте код с GitHub или официального репозитория).

sudo wget --no-check-certificate https://raw.githubusercontent.com/christgau/wsdd/master/src/wsdd.py -O /usr/bin/wsdd

Права: 
chmod +x /usr/bin/wsdd

Служба: Создайте /etc/systemd/system/wsdd.service:

Ini, TOML
[Unit]
Description=WSD host daemon
After=network.target

[Service]
ExecStart=/usr/bin/python3 /usr/bin/wsdd -w ВАШ_ДОМЕН -n ИМЯ_СЕРВЕРА
User=nobody
Group=nogroup

[Install]
WantedBy=multi-user.target
Запуск: systemctl enable --now wsdd

5. Настройка на стороне Windows
Тип сети: Убедитесь, что сеть помечена как «Частная» или «Доменная».

Службы: Должны быть запущены «Публикация ресурсов обнаружения функций» (FDResPub) и «Хост поставщика функции обнаружения» (FDPHost).


(спасибо гроку)

понедельник, 16 марта 2026 г.

Подключение к компьютеру, который находится за NATом

Появилась необходимость подключиться к изолированному компьютеру (технологический) специалисту из зарубежья, причем на компьютере установлен какой-то специализированный Linux от которого, к тому же, нет пароля, только меню на экране.
*приведено к читаемому и рабочему виду с помощью Грока, за что и спасибо, описано Клаудем, идея и реализация моя.

Играться с промежуточным компьютером можно, НО
есть 
4G-модем, бюджетный, просто дает интернет
и 
Mikrotik hAP Lite  - ультрабюджетный.

Зато есть где-то там, другой Микротик с выделенным IP, и извращенным админом, и так наш технологический компьютер подключается в сеть 192.168.1.х микротика, который образует туннель с базой, также туннель образует и специалист со своего зарубежья, и заходит на наш промкомпьютер специализированной программой.
Выбрано L2tp как простота реализации.


Анализ логики работы

Топология «звезда» (hub-and-spoke). Все клиенты подключаются только к центральному серверу — прямых туннелей между клиентами нет. Для связи MikroTik-клиент ↔ Windows-клиент трафик идёт через хаб: 192.168.1.199 → 192.168.166.1 → 192.168.166.x.

Стек протоколов. L2TP создаёт туннель на уровне 2 (PPP-сессия), IPsec шифрует этот туннель на уровне 3 (ESP + IKE). PPP внутри туннеля обеспечивает аутентификацию (MSCHAPv2/CHAP) и назначение адресов из пула vpn-pool.

Жизненный цикл соединения состоит из трёх фаз: (1) IKE-согласование (UDP 500/4500, PSK — предобщий ключ), (2) IPsec SA — установка шифрованного канала (протокол ESP), (3) L2TP + PPP поверх IPsec — туннель, аутентификация пользователя, получение IP из пула.

Маршрутизация. MikroTik-клиент получает адрес .166.x, но его локальная сеть 192.168.1.0/24 не анонсируется автоматически — для этого добавляется статический маршрут 192.168.166.0/24 via l2tp-to-hub на клиенте. Windows работает в режиме full-tunnel: весь трафик идёт через 192.168.166.1, что обеспечивает галочка «использовать шлюз по умолчанию».

Firewall-логика выстроена в два слоя: на сервере input-цепочка разрешает только служебные порты VPN (1701, 500, 4500, ESP), forward-цепочка разрешает трафик внутри interface-list=PPP-VPN. На MikroTik-клиенте правила forward разрешают двунаправленный обмен между туннелем и мостом LAN.

======================================

мини-шпаргалка по настройке L2TP/IPsec «звезды» на MikroTik (центральный сервер + MikroTik-клиент + Windows-клиент с full-tunnel), с акцентом на правильную последовательность и проверку перед применением.

1. Центральный сервер (L2TP-сервер)

Порядок выполнения:

1. Создать пул адресов для клиентов /ip pool add name=vpn-pool ranges=192.168.166.10-192.168.166.250

2. Создать / проверить профиль PPP

text

/ppp profile

add name=vpn-profile local-address=192.168.166.1 remote-address=vpn-pool \

    use-encryption=required change-tcp-mss=yes only-one=no

(если профиль уже есть → set вместо add)

3. Включить L2TP-сервер + IPsec

text


/interface l2tp-server server

set enabled=yes default-profile=vpn-profile authentication=mschap2,chap \

    use-ipsec=yes ipsec-secret=ВАШ_СЕКРЕТНЫЙ_КЛЮЧ

Создать пользователя для Windows text

/ppp secret

add name=win-user password=СильныйПароль service=l2tp profile=vpn-profile

Создать список интерфейсов PPP-VPN (если его ещё нет) text

/interface list print where name=PPP-VPN

→ если ничего не нашлось →

text


/interface list add name=PPP-VPN comment="VPN клиенты L2TP"

Привязать список к профилю (только после создания списка!) text

/ppp profile set [find name=vpn-profile] interface-list=PPP-VPN

Добавить базовые firewall-правила (input + forward) text

/ip firewall filter

add chain=input  action=accept protocol=udp dst-port=1701,500,4500 comment="L2TP/IPsec"

add chain=input  action=accept protocol=ipsec-esp

add chain=forward action=accept in-interface-list=PPP-VPN out-interface-list=PPP-VPN comment="VPN ↔ VPN"

MikroTik-клиент (тот, за которым 192.168.1.199) Порядок:

Создать L2TP-клиент text

/interface l2tp-client

add connect-to=ПУБЛИЧНЫЙ_IP_СЕРВЕРА user=имя_секрета password=пароль \

    name=l2tp-to-hub use-ipsec=yes ipsec-secret=ВАШ_СЕКРЕТНЫЙ_КЛЮЧ \

    add-default-route=no disabled=no

Дождаться подключения (Connected) /interface l2tp-client print

Добавить маршрут обратно к туннельной подсети text

/ip route add dst-address=192.168.166.0/24 gateway=l2tp-to-hub distance=1 check-gateway=ping

Разрешить трафик из туннеля в LAN и обратно text

/ip firewall filter

add chain=forward action=accept in-interface=l2tp-to-hub out-interface=bridge comment="туннель → LAN" place-before=0

add chain=forward action=accept in-interface=bridge out-interface=l2tp-to-hub comment="LAN → туннель" place-before=0

(bridge → замени на реальное имя интерфейса с 192.168.1.0/24)

5. Разрешить доступ к WebFig/Winbox с VPN (если нужно)

text


/ip firewall filter

add chain=input action=accept protocol=tcp dst-port=80,443,8291 \

    in-interface=l2tp-to-hub comment="Web/Winbox из VPN" place-before=0

Убедиться, что masquerade только на WAN /ip firewall nat print → не должно быть masquerade на l2tp-to-hub

Windows-клиент

Создать VPN-соединение: L2TP/IPsec с предв. ключом

Сервер = публичный IP сервера

Тип входа = имя пользователя и пароль

Предварительный ключ = ВАШ_СЕКРЕТНЫЙ_КЛЮЧ

В свойствах соединения → IPv4 → Дополнительно → Поставить галочку «Использовать шлюз по умолчанию в удалённой сети»

Подключиться → проверить:

ipconfig → IP из 192.168.166.x

route print → default gateway = 192.168.166.1

ping 192.168.1.199 Краткая проверка после всего

Сервер: /interface l2tp-server print → connected клиенты

Сервер: /interface list member print where list=PPP-VPN → есть l2tp-in*

Клиент: /ip route print → есть 192.168.166.0/24 через l2tp-to-hub

Windows: tracert 192.168.1.199 → проходит через 192.168.166.1

======================================

среда, 17 сентября 2025 г.

модуль отправки сообщений и фото в мессенджер Ватсап, перспективно сервис MessHub2

Аксиома:
 1. Бесплатно бота нам Американская Цель не дает
Поправки:
1. У меня есть браузер,
2.Я могу сохранить сессию в папке локального пользователя
3. Я могу нажимать на кнопки программно
Инструменты:
1. AI модели
2. Linux + X.
3. Venv
Мотивация:
1. Сделать это ( скрытое: что-то, кому-то доказать )
2. Альтернативная без телеграмм реализация, валидная от СБ в стране.
3. Производственная необходимость
Спонсор:
1. Человеческие условия труда

messhub2

16.09.2025.....наконец-то пошли текстовые сообщения, это прорыв, 1 неделя на нажатие кнопки отправить :) в тексте,  и 2 недели на окно с Картинкой без отправки.... - прорыв но отсылается картинка в качестве стикера.
17.09.2025....седые волосы, бан от моделей АИ....НО мы сделали это.....все оказалось проще чем сам путь, вопрос на долго ли.... Тестирование, не нравятся задержки интерфкйса, но это граничит с баном от компании.
21.09.2025...2е суток в реальных условиях испытаний следующей версии, коды выложу позже, есть часть (в виде сервиса) которая обслуживает очередь и кидает в мессенджер, вторая крутится возле прикладной программы подпрограммой и создает очередь, соприкосновение только папкой очереди. messhub2v4 
09.10.2025...ватсап решил отключить старые устройства...потому мы обновляем представление агента, посмотрим на сколько хватит...
chrome_options.add_argument("--user-agent=Mozilla/5.0 (iPad; CPU OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1")
13.10.2025 поменял Большой Брат селекторы...теперь "       
 # Нажимаем на кнопку "Прикрепить"
        attach_selectors = [
            (By.CSS_SELECTOR, 'span[data-icon="plus-rounded"]'),  # Новый селектор

воскресенье, 14 сентября 2025 г.

Подключение китайского регистратора от Hangzhou Xiongmai Technology Co. к регистратору HikVision по протоколу rtsp

Бесплатное ПО от  HikVision просто убивает вычислительные ресурсы ПК, понадобилось какое-то время чтобы я пришел к тому, что китайская плата регистратора может показывать rtsp потоки от регистратора  HikVision (Юра..ка представитель от Viatek, который "толакет" компы с игровыми видеокартами для поста видеонаблюдение - ПРИВЕТ!!!!).
====================

1. Альтернативным способом показывать поток в обычном проигрывателе:

у меня VLC и  PotRlayer
согласились показывать поток 7 от регистратора 192.168.0.9 в виде:
rtsp://login:password@192.168.0.9:554/Streaming/Channels/701
и дополнительный
rtsp://login:password@192.168.0.9:554/Streaming/Channels/702
====================

2.Регистратор на плате:  XM/HiSilicon  AHB8004R-MH-NVT

прошивка "V4.03.R11.00000227.12001.131700.0000000 BuildDate: 2023-09-18 16:32:09"
до этого НЕУДАЧНО пробовал на неновом регистраторе NBD8004R-PL с прошивкой "V4.03.R11.00000203.12001.130000.0000000 BuildDate: 2019-07-30 16:50:44", там есть поддержка onvif но нет rtsp.
Через сетевое ПО можно даже не пытаться ... у меня корректно занести устройство rtsp не удалось.
Итак только локально...


и случайно выбранный DVR вместо HVR, заставил погрузиться в конфиг, который можно забекапить....это обычный архив, в котором хранятся локальные конфиги общей системы регистратора, в котором есть папка JSON в которой опять в архивах хранятся файлы (в моей версии по 2: нынешний и предыдущий) и нас интересует NetWork~ в котором есть раздел нашей камеры внешнего регистратора 

{
  "ConnType": "SINGLE",
  "Decoder": [
    {
      "Channel": 0,
      "ConfName": "chConfig",
      "DevType": "HVR",
      "Enable": true,
      "IPAddress": "192.168.0.9",
      "Interval": 10,
      "MacAddr": "",
      "MainRtspUrl": "rtsp://192.168.0.9:554/Streaming/Channels/701",
      "PassWord": "test1290",
      "Port": 554,
      "Protocol": "RTSP",
      "SerialNo": "",
      "StreamType": "MAIN",
      "SubRtspUrl": "rtsp://192.168.0.9:554/Streaming/Channels/702",
      "TransModel": 0,
      "UserName": "test"
    }
  ],
  "EnCheckTime": true,
  "Enable": true,
  "SingleConnId": "0x00000001",
  "SynchResolution": true,
  "TourIntv": 10
}

т.е. если оооооочень постараться можно конфиги править и ручками....
у меня все решилось успешно до ручной правки конфигов, но перспективы для извращений есть :)



четверг, 28 августа 2025 г.

dokuwiki + .htapasswd + docker

 казалось бы простая "модификация" стандартного докера, заняла немало времени.
Что тут такого? пещерная "HTTP Basic Authentication" как лишняя проверка и слепость разных ИИ в интеграции настроек.

Итак:
cat docker-compose.yml
services:
  web:
    image: ghcr.io/linuxserver/dokuwiki:latest
    container_name: dokuwiki
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Etc/UTC
    volumes:
      - ./dokuwiki_config:/config
      - ./.htpasswd:/etc/nginx/.htpasswd
      - ./nginx-auth.conf:/config/nginx/site-confs/custom.conf
    ports:
      - "127.0.0.1:80"
      - "127.0.0.1:443"
    restart: unless-stopped
    network_mode: bridge
 cat nginx-auth.conf
# Добавление Basic Auth к существующему блоку server
auth_basic "Restricted Access";
auth_basic_user_file /etc/nginx/.htpasswd;

=============
.htpasswd делается стандартным способом
ну наличие папки dokuwiki_config в корне с этими файлами.
IP вместо 127.0.0.1 но можно и по другому.....
В папке:
docker compose up -d
docker compose logs -f

Плюшка:
попадаем внутрь докера, кривыми ручками:
docker exec -it dokuwiki /sbin/apk update
docker exec -it dokuwiki /sbin/apk add mc
docker exec -it dokuwiki mc
Но это только на сессию докера....может оно и к лучшему, все что нужно поменять на свое притягиваем через volumes в docker-compose.yml