اصلاح ورک فلو
flash-aminpa-V1.5.yml / Hardware flashing - Amin Pardaz Asia v1.5 (push) Failing after 6m9s

This commit is contained in:
2026-11-10 13:17:01 +03:30
والد 68583fd185
کامیت fc45c88708
4فایلهای تغییر یافته به همراه180 افزوده شده و 2 حذف شده
@@ -71,8 +71,8 @@ jobs:
- name: Configure and verify bootcmd for detected flash - name: Configure and verify bootcmd for detected flash
run: /usr/bin/python3 deployment/serial/serial_command.py set-bootcmd run: /usr/bin/python3 deployment/serial/serial_command.py set-bootcmd
- name: Save U-Boot environment before reboot # - name: Save U-Boot environment before reboot
run: /usr/bin/python3 deployment/serial/serial_command.py saveenv # run: /usr/bin/python3 deployment/serial/serial_command.py saveenv
- name: Reboot hardware after successful flashing - name: Reboot hardware after successful flashing
run: /usr/bin/python3 deployment/serial/serial_command.py reboot run: /usr/bin/python3 deployment/serial/serial_command.py reboot
@@ -0,0 +1,24 @@
name: init test
on:
workflow_dispatch:
jobs:
init-test:
runs-on: serial-runner
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Verify ordered boot and Linux console
env:
RUTBS_PASSWORD: ${{ secrets.RUTBS_PASSWORD }}
run: /usr/bin/python3 deployment/serial/init_test.py
- name: Upload redacted serial log
if: ${{ always() }}
uses: actions/upload-artifact@v3
with:
name: boot-log
path: artifacts/boot.log
if-no-files-found: error
+27
مشاهده پرونده
@@ -71,3 +71,30 @@ IP میزبان باید از قبل روی کارت شبکهٔ انتخاب‌
workflowهای اصلی امین‌پرداز و ورودی‌های workflowهای کمکی در workflowهای اصلی امین‌پرداز و ورودی‌های workflowهای کمکی در
`.gitea/workflows/` باقی می‌مانند و اسکریپت‌ها را از مسیر جدید اجرا می‌کنند. `.gitea/workflows/` باقی می‌مانند و اسکریپت‌ها را از مسیر جدید اجرا می‌کنند.
فایل‌های باینری همچنان در پوشهٔ `firmware/` ریشهٔ مخزن قرار می‌گیرند. فایل‌های باینری همچنان در پوشهٔ `firmware/` ریشهٔ مخزن قرار می‌گیرند.
## تست ترتیب بوت از طریق Serial
workflow دستی `init test` از فایل `.gitea/workflows/init-test.yml` اجرا می‌شود.
رمز دستگاه را با نام `RUTBS_PASSWORD` در **Actions → Secrets** مخزن تعریف کنید؛
رمز در سورس یا متغیرهای عمومی قرار نمی‌گیرد. رانر به `pyserial` و دسترسی
به پورت `/dev/ttyUSB0` نیاز دارد.
برای اجرای مستقیم، رمز را بدون نمایش در ترمینال دریافت کنید:
```bash
read -rs -p 'RUTBS password: ' RUTBS_PASSWORD
export RUTBS_PASSWORD
python3 deployment/serial/init_test.py
unset RUTBS_PASSWORD
```
برای پورت دیگر، گزینهٔ `--port /dev/ttyUSB1` را اضافه کنید.
هنگام اجرای تست، برد باید در پرامپت U-Boot یعنی `=>` باشد.
اسکریپت ابتدا با ارسال Enter پرامپت را تازه می‌کند و پس از تشخیص آن،
دستور `res` را برای ریبوت می‌فرستد؛ سپس مراحل بوت را به‌ترتیب بررسی می‌کند.
این تست باید پیام‌های انتخاب سطح دیباگ و فعال‌سازی کنسول را ببیند و
با workflow دیگری که همان پورت Serial را استفاده می‌کند هم‌زمان اجرا نشود.
از شروع تست، مهلت مشترک مراحل ۱۸۰ ثانیه است. تنها پس از ورود و دریافت
خروجی دقیق `Linux` از `uname -s`، پیام `BOOT TEST: PASS` و کد خروج صفر
برگردانده می‌شود. خطا کد خروج غیرصفر دارد. لاگ Serial با حذف رمز در
`artifacts/boot.log` ذخیره و حتی در شکست تست به‌صورت آرتیفکت بارگذاری می‌شود.
@@ -0,0 +1,127 @@
"""Restart RUTBS from U-Boot and verify the ordered boot and kernel name."""
import argparse
import fcntl
import os
import re
import time
from pathlib import Path
import serial
STAGES = (
('U-Boot prompt', '=>', b'res\r'),
('kernel', 'Starting kernel ...', None),
('debug selection', 'Press the [1], [2], [3] or [4] key and hit [enter] to select the debug level', None),
('console activation', 'Please press Enter to activate this console.', b'\r'),
('login', 'RUTBS login:', b'root\r'),
('password', 'Password:', 'password'),
('shell', 'root@RUTBS:~#', b'uname -s\r'),
('command result', 'root@RUTBS:~#', None),
)
FAILURE = re.compile(r'Kernel panic|Login incorrect|Login failed|Authentication fail(?:ed|ure)|Access denied', re.I)
def run_test(port, password, transcript, deadline):
stage = 0
pending = ''
observed = ''
# Request a fresh prompt when the existing => predates opening the port.
remaining = deadline - time.monotonic()
if remaining <= 0:
raise RuntimeError('Overall test timeout')
port.write_timeout = min(2, remaining)
if port.write(b'\r') != 1:
raise RuntimeError('Incomplete serial write')
while time.monotonic() < deadline:
data = port.read(min(port.in_waiting or 1, 4096))
if not data:
continue
transcript.extend(data)
chunk = data.decode('utf-8', errors='replace').replace('\r', '')
pending += chunk
observed = (observed + chunk)[-8192:]
if FAILURE.search(observed):
raise RuntimeError('Boot or authentication failure detected')
while stage < len(STAGES):
name, token, action = STAGES[stage]
if stage == 0:
match = re.search(r'(?:^|\n)[^\n]*=> *$', pending)
position = pending.rfind('=>') if match else -1
else:
position = pending.find(token)
if position < 0:
break
before = pending[:position]
pending = pending[position + len(token):]
if stage == len(STAGES) - 1:
lines = [line.strip() for line in before.splitlines() if line.strip()]
if lines and lines[0] == 'uname -s':
lines.pop(0) # A terminal may echo the command.
if lines != ['Linux']:
raise RuntimeError('uname -s output was not exactly Linux')
if time.monotonic() >= deadline:
raise RuntimeError('Overall test timeout')
return
if action is not None:
# Discard pre-existing input: the next prompt must follow our write.
pending = ''
payload = password.encode('utf-8') + b'\r' if action == 'password' else action
remaining = deadline - time.monotonic()
if remaining <= 0:
raise RuntimeError('Overall test timeout')
port.write_timeout = min(2, remaining)
if port.write(payload) != len(payload):
raise RuntimeError('Incomplete serial write')
stage += 1
if len(pending) > 65536:
raise RuntimeError('Unexpected excessive serial output in current stage')
raise RuntimeError('Timeout waiting for ' + STAGES[stage][0])
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--port', default='/dev/ttyUSB0')
args = parser.parse_args()
deadline = time.monotonic() + 180
password = os.environ.get('RUTBS_PASSWORD', '')
transcript = bytearray()
result = 1
reason = 'Serial connection or configuration error'
log = Path('artifacts/boot.log')
try:
log.parent.mkdir(parents=True, exist_ok=True)
# Never put raw serial bytes on disk or stdout, even during authentication.
log.write_text('', encoding='utf-8')
if not password or any(c in password for c in '\r\n\x00'):
raise RuntimeError('RUTBS_PASSWORD is missing or invalid')
with open('/tmp/uboot-serial.lock', 'w') as lock:
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
with serial.Serial(args.port, 115200, timeout=0.1, write_timeout=2, exclusive=True) as port:
run_test(port, password, transcript, deadline)
result = 0
except RuntimeError as exc:
reason = str(exc)
except Exception:
# Serial/library exceptions can include transmitted data; do not print them.
pass
finally:
content = transcript.decode('utf-8', errors='replace').replace('\r', '')
if password:
content = content.replace(password, '[REDACTED]')
reason = reason.replace(password, '[REDACTED]')
try:
log.write_text(content, encoding='utf-8')
except OSError:
result = 1
reason = 'Could not save serial log'
if result:
print('BOOT TEST: FAIL')
print(reason)
else:
print('BOOT TEST: PASS')
return result
if __name__ == '__main__':
raise SystemExit(main())