مقایسه کامیت‌‌ها

..
3 کامیت‌ها
مولف SHA1 پیام تاریخ
nearology fc45c88708 اصلاح ورک فلو
flash-aminpa-V1.5.yml / Hardware flashing - Amin Pardaz Asia v1.5 (push) Failing after 6m9s
2026-11-10 13:17:01 +03:30
nearology 68583fd185 اصلاح bootargs 2026-11-10 13:14:02 +03:30
nearology 0528032639 اصلاح ورک فلو های استقرار 2026-11-10 12:59:46 +03:30
16فایلهای تغییر یافته به همراه218 افزوده شده و 29 حذف شده
@@ -22,7 +22,7 @@ jobs:
CONFIRM_SQUASHFS_MTDBLOCK3: ${{ inputs.confirm_squashfs_mtdblock3 }} CONFIRM_SQUASHFS_MTDBLOCK3: ${{ inputs.confirm_squashfs_mtdblock3 }}
run: | run: |
if [ "$CONFIRM_SQUASHFS_MTDBLOCK3" = "true" ]; then if [ "$CONFIRM_SQUASHFS_MTDBLOCK3" = "true" ]; then
/usr/bin/python3 scripts/serial_command.py boot-config --confirm-squashfs-mtdblock3 /usr/bin/python3 deployment/serial/serial_command.py boot-config --confirm-squashfs-mtdblock3
else else
/usr/bin/python3 scripts/serial_command.py boot-config /usr/bin/python3 deployment/serial/serial_command.py boot-config
fi fi
+1 -1
مشاهده پرونده
@@ -12,4 +12,4 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Detect flash, set bootcmd and verify - name: Detect flash, set bootcmd and verify
run: /usr/bin/python3 scripts/serial_command.py set-bootcmd run: /usr/bin/python3 deployment/serial/serial_command.py set-bootcmd
@@ -13,4 +13,4 @@ jobs:
- name: Transfer DTB, write SPI flash and verify read-back - name: Transfer DTB, write SPI flash and verify read-back
run: | run: |
/usr/bin/python3 scripts/serial_command.py transfer-dtb /usr/bin/python3 deployment/serial/serial_command.py transfer-dtb
@@ -38,41 +38,41 @@ jobs:
PY PY
- name: Check current U-Boot CLI - name: Check current U-Boot CLI
run: /usr/bin/python3 scripts/serial_command.py check run: /usr/bin/python3 deployment/serial/serial_command.py check
- name: Configure and verify network - name: Configure and verify network
env: env:
UBOOT_DEVICE_IP: ${{ vars.UBOOT_DEVICE_IP || '192.168.75.100' }} UBOOT_DEVICE_IP: ${{ vars.UBOOT_DEVICE_IP || '192.168.75.100' }}
TFTP_SERVER_IP: ${{ vars.TFTP_SERVER_IP || '192.168.75.247' }} TFTP_SERVER_IP: ${{ vars.TFTP_SERVER_IP || '192.168.75.247' }}
run: | run: |
/usr/bin/python3 scripts/serial_command.py configure --device-ip "$UBOOT_DEVICE_IP" --tftp-server-ip "$TFTP_SERVER_IP" /usr/bin/python3 deployment/serial/serial_command.py configure --device-ip "$UBOOT_DEVICE_IP" --tftp-server-ip "$TFTP_SERVER_IP"
- name: Check all firmware sizes against detected flash layout - name: Check all firmware sizes against detected flash layout
run: /usr/bin/python3 scripts/serial_command.py preflight run: /usr/bin/python3 deployment/serial/serial_command.py preflight
- name: Erase detected SPI flash and verify sample - name: Erase detected SPI flash and verify sample
run: /usr/bin/python3 scripts/serial_command.py erase run: /usr/bin/python3 deployment/serial/serial_command.py erase
- name: Transfer, write and verify U-Boot - name: Transfer, write and verify U-Boot
run: /usr/bin/python3 scripts/serial_command.py transfer run: /usr/bin/python3 deployment/serial/serial_command.py transfer
- name: Transfer, write and verify DTB - name: Transfer, write and verify DTB
run: /usr/bin/python3 scripts/serial_command.py transfer-dtb run: /usr/bin/python3 deployment/serial/serial_command.py transfer-dtb
- name: Transfer, write and verify Kernel - name: Transfer, write and verify Kernel
run: /usr/bin/python3 scripts/serial_command.py transfer-kernel run: /usr/bin/python3 deployment/serial/serial_command.py transfer-kernel
- name: Transfer, write and verify RootFS - name: Transfer, write and verify RootFS
run: /usr/bin/python3 scripts/serial_command.py transfer-rootfs run: /usr/bin/python3 deployment/serial/serial_command.py transfer-rootfs
- name: Inspect boot configuration - name: Inspect boot configuration
run: /usr/bin/python3 scripts/serial_command.py boot-config run: /usr/bin/python3 deployment/serial/serial_command.py boot-config
- name: Configure and verify bootcmd for detected flash - name: Configure and verify bootcmd for detected flash
run: /usr/bin/python3 scripts/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 scripts/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 scripts/serial_command.py reboot run: /usr/bin/python3 deployment/serial/serial_command.py reboot
@@ -13,4 +13,4 @@ jobs:
- name: Erase entire flash and verify first 256 bytes - name: Erase entire flash and verify first 256 bytes
run: | run: |
/usr/bin/python3 scripts/serial_command.py erase /usr/bin/python3 deployment/serial/serial_command.py erase
@@ -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
@@ -13,4 +13,4 @@ jobs:
- name: Transfer Kernel, write SPI flash and verify read-back - name: Transfer Kernel, write SPI flash and verify read-back
run: | run: |
/usr/bin/python3 scripts/serial_command.py transfer-kernel /usr/bin/python3 deployment/serial/serial_command.py transfer-kernel
@@ -13,4 +13,4 @@ jobs:
- name: Detect flash, transfer RootFS and verify read-back - name: Detect flash, transfer RootFS and verify read-back
run: | run: |
/usr/bin/python3 scripts/serial_command.py transfer-rootfs /usr/bin/python3 deployment/serial/serial_command.py transfer-rootfs
+1 -1
مشاهده پرونده
@@ -13,4 +13,4 @@ jobs:
- name: Check current U-Boot CLI prompt - name: Check current U-Boot CLI prompt
run: | run: |
/usr/bin/python3 scripts/serial_command.py check /usr/bin/python3 deployment/serial/serial_command.py check
@@ -27,4 +27,4 @@ jobs:
TEST_HOST_IP: ${{ vars.TEST_HOST_IP || '192.168.75.15' }} TEST_HOST_IP: ${{ vars.TEST_HOST_IP || '192.168.75.15' }}
TEST_DEVICE_IP: ${{ vars.TEST_DEVICE_IP }} TEST_DEVICE_IP: ${{ vars.TEST_DEVICE_IP }}
run: | run: |
/usr/bin/python3 scripts/software_test.py --interface "$TEST_INTERFACE" --host-ip "$TEST_HOST_IP" --device-ip "$TEST_DEVICE_IP" /usr/bin/python3 deployment/serial/software_test.py --interface "$TEST_INTERFACE" --host-ip "$TEST_HOST_IP" --device-ip "$TEST_DEVICE_IP"
@@ -16,4 +16,4 @@ jobs:
UBOOT_DEVICE_IP: ${{ vars.UBOOT_DEVICE_IP || '192.168.75.100' }} UBOOT_DEVICE_IP: ${{ vars.UBOOT_DEVICE_IP || '192.168.75.100' }}
TFTP_SERVER_IP: ${{ vars.TFTP_SERVER_IP || '192.168.75.247' }} TFTP_SERVER_IP: ${{ vars.TFTP_SERVER_IP || '192.168.75.247' }}
run: | run: |
/usr/bin/python3 scripts/serial_command.py configure --device-ip "$UBOOT_DEVICE_IP" --tftp-server-ip "$TFTP_SERVER_IP" /usr/bin/python3 deployment/serial/serial_command.py configure --device-ip "$UBOOT_DEVICE_IP" --tftp-server-ip "$TFTP_SERVER_IP"
@@ -13,4 +13,4 @@ jobs:
- name: Transfer firmware, write SPI flash and verify read-back - name: Transfer firmware, write SPI flash and verify read-back
run: | run: |
/usr/bin/python3 scripts/serial_command.py transfer /usr/bin/python3 deployment/serial/serial_command.py transfer
+40 -2
مشاهده پرونده
@@ -21,7 +21,7 @@ workflow تنظیم شبکه و workflow اصلی فلش از آن‌ها است
نمونهٔ اجرای مستقیم: نمونهٔ اجرای مستقیم:
```bash ```bash
/usr/bin/python3 scripts/serial_command.py configure \ /usr/bin/python3 deployment/serial/serial_command.py configure \
--device-ip 192.168.75.100 \ --device-ip 192.168.75.100 \
--tftp-server-ip 192.168.75.247 --tftp-server-ip 192.168.75.247
``` ```
@@ -53,10 +53,48 @@ IP میزبان باید از قبل روی کارت شبکهٔ انتخاب‌
نمونهٔ اجرای دستی روی runner با جایگزین‌کردن IP واقعی دستگاه: نمونهٔ اجرای دستی روی runner با جایگزین‌کردن IP واقعی دستگاه:
```bash ```bash
/usr/bin/python3 scripts/software_test.py \ /usr/bin/python3 deployment/serial/software_test.py \
--interface eno1 \ --interface eno1 \
--host-ip 192.168.75.15 \ --host-ip 192.168.75.15 \
--device-ip <DEVICE_IP> --device-ip <DEVICE_IP>
``` ```
برای محل فایل‌های firmware و نقشهٔ فلش، [راهنمای firmware](firmware/README.md) را ببینید. برای محل فایل‌های firmware و نقشهٔ فلش، [راهنمای firmware](firmware/README.md) را ببینید.
## ساختار استقرار
اسکریپت‌های اجرایی در `deployment/serial/` قرار دارند:
- `serial_command.py`: کنترل U-Boot، فلش و راه‌اندازی برد از طریق سریال.
- `software_test.py`: بررسی پاسخ ping دستگاه پس از بوت.
workflowهای اصلی امین‌پرداز و ورودی‌های workflowهای کمکی در
`.gitea/workflows/` باقی می‌مانند و اسکریپت‌ها را از مسیر جدید اجرا می‌کنند.
فایل‌های باینری همچنان در پوشهٔ `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())
@@ -206,7 +206,7 @@ def boot_config(cli, confirmed=False):
if not confirmed: if not confirmed:
print("\nBoot configuration inspected; no changes requested.") print("\nBoot configuration inspected; no changes requested.")
return return
bootargs = "console=ttyS0,115200 loglevel=8 root=/dev/mtdblock3 rootfstype=squashfs ro rootwait" bootargs = "console=ttyS0,115200 loglevel=1 root=/dev/mtdblock3 rootfstype=squashfs ro rootwait"
cli.command("setenv bootargs '" + bootargs + "'") cli.command("setenv bootargs '" + bootargs + "'")
cli.command("printenv bootargs", r"^bootargs=" + re.escape(bootargs) + r"$") cli.command("printenv bootargs", r"^bootargs=" + re.escape(bootargs) + r"$")
print("\nBootargs verified in RAM; environment not saved and board not rebooted.") print("\nBootargs verified in RAM; environment not saved and board not rebooted.")
@@ -224,7 +224,7 @@ def main():
args = parser.parse_args() args = parser.parse_args()
if args.firmware is None: if args.firmware is None:
filename = "rootfs.squashfs" if args.mode == "transfer-rootfs" else "zImage" if args.mode == "transfer-kernel" else ("sun8i-v3s-licheepi-zero-dock.dtb" if args.mode == "transfer-dtb" else "u-boot-sunxi-with-spl.bin") filename = "rootfs.squashfs" if args.mode == "transfer-rootfs" else "zImage" if args.mode == "transfer-kernel" else ("sun8i-v3s-licheepi-zero-dock.dtb" if args.mode == "transfer-dtb" else "u-boot-sunxi-with-spl.bin")
args.firmware = Path(__file__).resolve().parent.parent / "firmware" / filename args.firmware = Path(__file__).resolve().parents[2] / "firmware" / filename
if args.mode in ("transfer", "transfer-dtb", "transfer-kernel", "transfer-rootfs") and not args.firmware.is_file(): if args.mode in ("transfer", "transfer-dtb", "transfer-kernel", "transfer-rootfs") and not args.firmware.is_file():
raise RuntimeError("Firmware file not found: " + str(args.firmware)) raise RuntimeError("Firmware file not found: " + str(args.firmware))
# Shared by both workflows: fail rather than interleave serial commands. # Shared by both workflows: fail rather than interleave serial commands.
@@ -250,7 +250,7 @@ def main():
files = {"uboot": "u-boot-sunxi-with-spl.bin", "dtb": "sun8i-v3s-licheepi-zero-dock.dtb", files = {"uboot": "u-boot-sunxi-with-spl.bin", "dtb": "sun8i-v3s-licheepi-zero-dock.dtb",
"kernel": "zImage", "rootfs": "rootfs.squashfs"} "kernel": "zImage", "rootfs": "rootfs.squashfs"}
for part, filename in files.items(): for part, filename in files.items():
path = Path(__file__).resolve().parent.parent / "firmware" / filename path = Path(__file__).resolve().parents[2] / "firmware" / filename
minimum = 64 if part in ("kernel", "rootfs") else 32 minimum = 64 if part in ("kernel", "rootfs") else 32
limit = LAYOUTS[capacity][part][1] limit = LAYOUTS[capacity][part][1]
if not path.is_file() or not minimum <= path.stat().st_size <= limit: if not path.is_file() or not minimum <= path.stat().st_size <= limit: