From febecf4dd920ffb5107d9986ca0bb7616aa0d76b Mon Sep 17 00:00:00 2001 From: TolaMironcenko Date: Sat, 14 Mar 2026 05:59:41 +0700 Subject: [PATCH 1/5] added temperature monitor --- README.md | 2 + src/main.c | 114 +++++++++++++++++++++++++++++++++++++++++++++++++ web/app.js | 35 +++++++++++++++ web/index.html | 8 ++++ web/style.css | 15 +++++++ 5 files changed, 174 insertions(+) diff --git a/README.md b/README.md index 61427e6..85ec68b 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ - **Авто-монтирование**: Автоматически монтирует `/dev/mmcblk1p1` в `/media` - **Поддержка больших файлов**: Загрузка файлов до 200 MB - **Перезагрузка системы**: Кнопка безопасной перезагрузки с корректным отмонтированием +- **Мониторинг температуры**: Отображение температуры CPU с цветовой индикацией ## Структура проекта @@ -86,6 +87,7 @@ sudo ./up | `/api/extlinux` | POST | Запись extlinux.conf | | `/api/progress` | GET | Статус загрузки | | `/api/mount` | GET | Статус монтирования | +| `/api/temperature` | GET | Температура CPU | | `/api/reboot` | POST | Перезагрузка системы | ## Параметры загрузки diff --git a/src/main.c b/src/main.c index 186b9a3..2985eff 100644 --- a/src/main.c +++ b/src/main.c @@ -282,6 +282,118 @@ static void handle_mount_status(struct mg_connection *c) { cJSON_Delete(root); } +// Handle temperature read +static void handle_temperature(struct mg_connection *c) { + float temperature = 0.0f; + int success = 0; + char path[256]; + + // Try all thermal zones (0-3) + for (int i = 0; i < 4 && !success; i++) { + snprintf(path, sizeof(path), "/sys/class/thermal/thermal_zone%d/temp", i); + FILE *f = fopen(path, "r"); + if (f) { + // Check zone type if available + char type_path[256]; + snprintf(type_path, sizeof(type_path), "/sys/class/thermal/thermal_zone%d/type", i); + FILE *type_f = fopen(type_path, "r"); + if (type_f) { + char type[32] = {0}; + if (fscanf(type_f, "%31s", type) == 1) { + // Prefer CPU or soc zones + int is_cpu = (strstr(type, "cpu") != NULL || + strstr(type, "soc") != NULL || + strstr(type, "CPU") != NULL); + + int temp_raw; + if (fscanf(f, "%d", &temp_raw) == 1) { + temperature = temp_raw / 1000.0f; + success = 1; + // If this is a CPU zone, use it immediately + if (is_cpu) { + fclose(f); + fclose(type_f); + break; + } + } + } + fclose(type_f); + } + fclose(f); + } + } + + // If thermal zones failed, try hwmon (0-3) + if (!success) { + for (int i = 0; i < 4 && !success; i++) { + snprintf(path, sizeof(path), "/sys/class/hwmon/hwmon%d/temp1_input", i); + FILE *f = fopen(path, "r"); + if (f) { + // Check sensor name + char name_path[256]; + snprintf(name_path, sizeof(name_path), "/sys/class/hwmon/hwmon%d/name", i); + FILE *name_f = fopen(name_path, "r"); + if (name_f) { + char name[32] = {0}; + if (fscanf(name_f, "%31s", name) == 1) { + // Prefer CPU or sunxi sensors + int is_cpu = (strstr(name, "cpu") != NULL || + strstr(name, "sunxi") != NULL || + strstr(name, "CPU") != NULL || + strstr(name, "acpitz") != NULL); + + int temp_raw; + if (fscanf(f, "%d", &temp_raw) == 1) { + temperature = temp_raw / 1000.0f; + success = 1; + if (is_cpu) { + fclose(f); + fclose(name_f); + break; + } + } + } + fclose(name_f); + } + fclose(f); + } + } + } + + // Last resort: try any temp1_input + if (!success) { + const char *fallback_paths[] = { + "/sys/class/hwmon/hwmon0/temp1_input", + "/sys/class/hwmon/hwmon1/temp1_input", + "/sys/class/hwmon/hwmon2/temp1_input", + "/sys/class/hwmon/hwmon3/temp1_input", + NULL + }; + + for (int i = 0; fallback_paths[i] != NULL && !success; i++) { + FILE *f = fopen(fallback_paths[i], "r"); + if (f) { + int temp_raw; + if (fscanf(f, "%d", &temp_raw) == 1) { + temperature = temp_raw / 1000.0f; + success = 1; + } + fclose(f); + } + } + } + + cJSON *root = cJSON_CreateObject(); + cJSON_AddBoolToObject(root, "success", success); + cJSON_AddNumberToObject(root, "temperature", temperature); + cJSON_AddStringToObject(root, "unit", "Celsius"); + + char *json = cJSON_PrintUnformatted(root); + mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s\n", json); + free(json); + cJSON_Delete(root); +} + // Handle system reboot static void handle_reboot(struct mg_connection *c) { cJSON *root = cJSON_CreateObject(); @@ -417,6 +529,8 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) { handle_progress(c); } else if (mg_match_uri(hm->uri, "/api/mount")) { handle_mount_status(c); + } else if (mg_match_uri(hm->uri, "/api/temperature")) { + handle_temperature(c); } else if (mg_match_uri(hm->uri, "/api/reboot")) { handle_reboot(c); } else if (mg_match_uri(hm->uri, "/")) { diff --git a/web/app.js b/web/app.js index 00a264f..7dc107d 100644 --- a/web/app.js +++ b/web/app.js @@ -21,7 +21,9 @@ document.addEventListener("DOMContentLoaded", function () { // Initialize checkMountStatus(); + checkTemperature(); setInterval(checkMountStatus, 10000); // Check every 10 seconds + setInterval(checkTemperature, 5000); // Check temperature every 5 seconds // Tab switching tabBtns.forEach((btn) => { @@ -306,4 +308,37 @@ document.addEventListener("DOMContentLoaded", function () { const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; } + + // Temperature functions + const temperatureStatus = document.getElementById("temperature-status"); + const tempValue = temperatureStatus + ? temperatureStatus.querySelector(".temp-value") + : null; + + async function checkTemperature() { + if (!tempValue) return; + + try { + const response = await fetch("/api/temperature"); + const data = await response.json(); + + if (data.success) { + tempValue.textContent = data.temperature.toFixed(1); + tempValue.style.color = getTempColor(data.temperature); + } else { + tempValue.textContent = "N/A"; + tempValue.style.color = "#888"; + } + } catch (error) { + tempValue.textContent = "Error"; + tempValue.style.color = "#ff4757"; + } + } + + function getTempColor(temp) { + if (temp < 50) return "#00ff88"; // Green - cool + if (temp < 70) return "#00d9ff"; // Blue - normal + if (temp < 80) return "#ffa500"; // Orange - warm + return "#ff4757"; // Red - hot + } }); diff --git a/web/index.html b/web/index.html index 90c894b..cedc67f 100644 --- a/web/index.html +++ b/web/index.html @@ -33,6 +33,14 @@ Checking... +
+ CPU Temperature: + + 🌡️ + -- + °C + +
diff --git a/web/style.css b/web/style.css index 96b0614..dc83b1a 100644 --- a/web/style.css +++ b/web/style.css @@ -98,6 +98,21 @@ h2 { box-shadow: 0 0 10px #ff4757; } +/* Temperature Display */ +.temp-icon { + margin-right: 8px; +} + +.temp-value { + color: #00d9ff; + font-size: 1.3rem; +} + +.temp-unit { + color: #888; + font-size: 0.9rem; +} + /* Upload Section */ .upload-tabs { display: flex; -- 2.54.0 From 844f839c51dd5159fdf094bb7a0567e34944fb9c Mon Sep 17 00:00:00 2001 From: TolaMironcenko Date: Sat, 14 Mar 2026 06:38:19 +0700 Subject: [PATCH 2/5] added upload uboot --- README.md | 7 +- src/main.c | 204 ++++++++++++++++++++++++++++++++++++++++++------- web/app.js | 8 ++ web/index.html | 8 ++ web/style.css | 15 ++++ 5 files changed, 215 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 85ec68b..8d47ac8 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,14 @@ - **Обновление ядра**: Загрузка `Image.gz` в boot раздел - **Обновление Device-Tree**: Загрузка `sun50i-h618-orangepi-zero2w.dtb` - **U-Boot Environment**: Загрузка `uboot.env` +- **U-Boot Binary**: Загрузка `u-boot-sunxi-with-spl.bin` с записью в: + - `/media/u-boot-sunxi-with-spl.bin` (файл) + - `/dev/mtd0` (SPI NOR flash) + - `/dev/mmcblk1` со смещением 8KB (SD карта, аналог `dd seek=8`) - **Редактор extlinux.conf**: Просмотр и редактирование конфигурации загрузчика - **Индикатор прогресса**: Отображение прогресса загрузки в реальном времени - **Авто-монтирование**: Автоматически монтирует `/dev/mmcblk1p1` в `/media` -- **Поддержка больших файлов**: Загрузка файлов до 200 MB +- **Поддержка больших файлов**: Загрузка файлов до 200 MB (до 1 MB для U-Boot Binary) - **Перезагрузка системы**: Кнопка безопасной перезагрузки с корректным отмонтированием - **Мониторинг температуры**: Отображение температуры CPU с цветовой индикацией @@ -103,6 +107,7 @@ sudo ./up | Ядро | `/media/Image.gz` | | Device-Tree | `/media/sun50i-h618-orangepi-zero2w.dtb` | | U-Boot Env | `/media/uboot.env` | +| U-Boot Binary | `/media/u-boot-sunxi-with-spl.bin` + `/dev/mtd0` + `/dev/mmcblk1` (+8KB offset) | | Extlinux | `/media/extlinux/extlinux.conf` | ## Конфигурация diff --git a/src/main.c b/src/main.c index 2985eff..08d31cf 100644 --- a/src/main.c +++ b/src/main.c @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include #include @@ -18,9 +20,11 @@ #define KERNEL_FILE "Image.gz" #define DTB_FILE "sun50i-h618-orangepi-zero2w.dtb" #define UBOOT_FILE "uboot.env" +#define UBOOT_BIN_FILE "u-boot-sunxi-with-spl.bin" #define EXTLINUX_FILE "extlinux/extlinux.conf" #define MAX_FILE_SIZE (200 * 1024 * 1024) // 200 MB +#define UBOOT_MAX_SIZE (1 * 1024 * 1024) // 1 MB for U-Boot static const char *s_http_port = "http://0.0.0.0:43243"; static struct mg_mgr mgr; @@ -100,9 +104,135 @@ static const char* get_target_filename(const char *type) { if (strcmp(type, "kernel") == 0) return KERNEL_FILE; if (strcmp(type, "dtb") == 0) return DTB_FILE; if (strcmp(type, "uboot") == 0) return UBOOT_FILE; + if (strcmp(type, "uboot_bin") == 0) return UBOOT_BIN_FILE; return NULL; } +// Write U-Boot to SPI NOR (mtd0) and MMC (mmcblk1) +static int write_uboot_to_devices(const char *data, size_t len, const char *filepath) { + int success = 0; + size_t chunk_size = 4096; + + // Allwinner H618 boot offset: 8KB (same as "bs=1024 seek=8" in dd) + #define MMC_BOOT_OFFSET 8192 + + printf("Writing U-Boot to devices (%zu bytes)...\n", len); + + // Check size limit for SPI NOR (safe limit to avoid overwriting partitions) + if (len > UBOOT_MAX_SIZE) { + printf("U-Boot image too large for SPI NOR (%zu > %d bytes)\n", len, UBOOT_MAX_SIZE); + return -1; + } + + // First, save to file in /media + FILE *f = fopen(filepath, "wb"); + if (!f) { + perror("fopen uboot file"); + return -1; + } + + size_t written = 0; + while (written < len) { + size_t to_write = (len - written < chunk_size) ? (len - written) : chunk_size; + if (fwrite(data + written, 1, to_write, f) != to_write) { + fclose(f); + return -1; + } + written += to_write; + g_upload.progress = (int)(written * 30 / len); // First 30% for file write + } + fclose(f); + sync(); + + // Write to /dev/mtd0 (SPI NOR flash) + printf("Writing to /dev/mtd0 (SPI NOR)...\n"); + int mtd_fd = open("/dev/mtd0", O_RDWR | O_SYNC); + if (mtd_fd >= 0) { + // Get erase block size for SPI NOR + struct mtd_info_user mtd_info; + memset(&mtd_info, 0, sizeof(mtd_info)); + if (ioctl(mtd_fd, MEMGETINFO, &mtd_info) == 0) { + printf("SPI NOR info: type=%s, size=%u bytes, erasesize=%u bytes\n", + mtd_info.type == MTD_NORFLASH ? "NOR" : "UNKNOWN", + mtd_info.size, mtd_info.erasesize); + + // Erase SPI NOR before writing + printf("Erasing SPI NOR...\n"); + struct erase_info_user erase_info; + erase_info.start = 0; + erase_info.length = mtd_info.size; // Erase entire chip + + if (ioctl(mtd_fd, MEMERASE, &erase_info) == 0) { + printf("SPI NOR erased successfully\n"); + } else { + perror("MEMERASE failed"); + printf("Continuing anyway (some systems auto-erase)...\n"); + } + } else { + perror("MEMGETINFO failed"); + printf("Could not get SPI NOR info, proceeding with write...\n"); + } + + // Write to SPI NOR + written = 0; + while (written < len) { + size_t to_write = (len - written < chunk_size) ? (len - written) : chunk_size; + ssize_t w = write(mtd_fd, data + written, to_write); + if (w < 0) { + perror("write mtd0 (SPI NOR)"); + close(mtd_fd); + break; + } + written += w; + g_upload.progress = 30 + (int)(written * 35 / len); // 30-65% for SPI NOR + } + close(mtd_fd); + printf("Written %zu bytes to /dev/mtd0 (SPI NOR)\n", written); + success = 1; + } else { + perror("open /dev/mtd0"); + printf("/dev/mtd0 (SPI NOR) not available, skipping...\n"); + } + + // Write to /dev/mmcblk1 at offset 8KB (Allwinner H618 boot location) + // This matches: dd if=u-boot.bin of=/dev/mmcblk1 bs=1024 seek=8 + printf("Writing to /dev/mmcblk1 at offset %d (SD card boot area)...\n", MMC_BOOT_OFFSET); + int mmc_fd = open("/dev/mmcblk1", O_RDWR | O_SYNC); + if (mmc_fd >= 0) { + // Seek to 8KB offset (sector 16 for 512-byte sectors) + if (lseek(mmc_fd, MMC_BOOT_OFFSET, SEEK_SET) != MMC_BOOT_OFFSET) { + perror("lseek mmcblk1"); + close(mmc_fd); + } else { + written = 0; + while (written < len) { + size_t to_write = (len - written < chunk_size) ? (len - written) : chunk_size; + ssize_t w = write(mmc_fd, data + written, to_write); + if (w < 0) { + perror("write mmcblk1"); + close(mmc_fd); + break; + } + written += w; + g_upload.progress = 65 + (int)(written * 35 / len); // 65-100% for MMC + } + close(mmc_fd); + printf("Written %zu bytes to /dev/mmcblk1 at offset %d\n", written, MMC_BOOT_OFFSET); + success = 1; + } + } else { + perror("open /dev/mmcblk1"); + printf("/dev/mmcblk1 not available, skipping...\n"); + } + + // Final sync to ensure all data is flushed + sync(); + + g_upload.progress = 100; + + return success ? 0 : -1; +} + // Get MIME type based on file extension static const char* get_mime_type(const char *path) { const char *ext = strrchr(path, '.'); @@ -301,10 +431,10 @@ static void handle_temperature(struct mg_connection *c) { char type[32] = {0}; if (fscanf(type_f, "%31s", type) == 1) { // Prefer CPU or soc zones - int is_cpu = (strstr(type, "cpu") != NULL || + int is_cpu = (strstr(type, "cpu") != NULL || strstr(type, "soc") != NULL || strstr(type, "CPU") != NULL); - + int temp_raw; if (fscanf(f, "%d", &temp_raw) == 1) { temperature = temp_raw / 1000.0f; @@ -337,11 +467,11 @@ static void handle_temperature(struct mg_connection *c) { char name[32] = {0}; if (fscanf(name_f, "%31s", name) == 1) { // Prefer CPU or sunxi sensors - int is_cpu = (strstr(name, "cpu") != NULL || + int is_cpu = (strstr(name, "cpu") != NULL || strstr(name, "sunxi") != NULL || strstr(name, "CPU") != NULL || strstr(name, "acpitz") != NULL); - + int temp_raw; if (fscanf(f, "%d", &temp_raw) == 1) { temperature = temp_raw / 1000.0f; @@ -369,7 +499,7 @@ static void handle_temperature(struct mg_connection *c) { "/sys/class/hwmon/hwmon3/temp1_input", NULL }; - + for (int i = 0; fallback_paths[i] != NULL && !success; i++) { FILE *f = fopen(fallback_paths[i], "r"); if (f) { @@ -456,8 +586,14 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) { return; } - if (file_data.len > MAX_FILE_SIZE) { - mg_http_reply(c, 400, "", "{\"error\":\"File too large (max 200MB)\"}\n"); + // Check file size limits + size_t max_size = MAX_FILE_SIZE; + if (strcmp(type, "uboot_bin") == 0) { + max_size = UBOOT_MAX_SIZE; // 1MB limit for U-Boot (safe for boot area) + } + + if (file_data.len > max_size) { + mg_http_reply(c, 400, "", "{\"error\":\"File too large\"}\n"); return; } @@ -477,32 +613,48 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) { printf("Upload: %s (%zu bytes) -> %s\n", type, file_data.len, filepath); - // Write file in chunks - FILE *fp = fopen(filepath, "wb"); - if (!fp) { - mg_http_reply(c, 500, "", "{\"error\":\"Failed to create file\"}\n"); - return; - } + // Update upload state + memcpy(g_upload.type, type, strlen(type) + 1); + g_upload.received = 0; + g_upload.progress = 0; + snprintf(g_upload.status, sizeof(g_upload.status), "Uploading..."); - size_t written = 0; - size_t chunk_size = 65536; - while (written < file_data.len) { - size_t to_write = (file_data.len - written < chunk_size) ? - (file_data.len - written) : chunk_size; - if (fwrite(file_data.buf + written, 1, to_write, fp) != to_write) { - fclose(fp); - mg_http_reply(c, 500, "", "{\"error\":\"Failed to write file\"}\n"); + // Special handling for U-Boot binary + if (strcmp(type, "uboot_bin") == 0) { + if (write_uboot_to_devices(file_data.buf, file_data.len, filepath) < 0) { + snprintf(g_upload.status, sizeof(g_upload.status), "Failed"); + mg_http_reply(c, 500, "", "{\"error\":\"Failed to write U-Boot\"}\n"); + return; + } + } else { + // Write file in chunks for other types + FILE *fp = fopen(filepath, "wb"); + if (!fp) { + mg_http_reply(c, 500, "", "{\"error\":\"Failed to create file\"}\n"); return; } - written += to_write; - } - fclose(fp); - sync(); + size_t written = 0; + size_t chunk_size = 65536; + while (written < file_data.len) { + size_t to_write = (file_data.len - written < chunk_size) ? + (file_data.len - written) : chunk_size; + if (fwrite(file_data.buf + written, 1, to_write, fp) != to_write) { + fclose(fp); + mg_http_reply(c, 500, "", "{\"error\":\"Failed to write file\"}\n"); + return; + } + written += to_write; + g_upload.progress = (int)(written * 100 / file_data.len); + } + + fclose(fp); + sync(); + g_upload.progress = 100; + } printf("Upload complete: %s\n", filepath); - memcpy(g_upload.type, type, strlen(type) + 1); g_upload.received = file_data.len; g_upload.progress = 100; snprintf(g_upload.status, sizeof(g_upload.status), "Complete"); diff --git a/web/app.js b/web/app.js index 7dc107d..195f098 100644 --- a/web/app.js +++ b/web/app.js @@ -15,6 +15,7 @@ document.addEventListener("DOMContentLoaded", function () { const mountStatus = document.getElementById("mount-status"); const extlinuxEditor = document.getElementById("extlinux-editor"); const editorStatus = document.getElementById("editor-status"); + const uploadHint = document.getElementById("upload-hint"); // Tab buttons const tabBtns = document.querySelectorAll(".tab-btn"); @@ -32,6 +33,13 @@ document.addEventListener("DOMContentLoaded", function () { this.classList.add("active"); uploadType.value = this.dataset.type; hideResult(); + + // Show/hide U-Boot warning + if (this.dataset.type === "uboot_bin") { + uploadHint.classList.add("visible"); + } else { + uploadHint.classList.remove("visible"); + } }); }); diff --git a/web/index.html b/web/index.html index cedc67f..623e9a3 100644 --- a/web/index.html +++ b/web/index.html @@ -56,6 +56,9 @@ +
@@ -81,6 +84,11 @@
+
+ ⚠️ U-Boot Binary will be written to SPI NOR + (/dev/mtd0) and SD card at 8KB offset (/dev/mmcblk1 + +8KB) +
diff --git a/web/style.css b/web/style.css index dc83b1a..eecd3e2 100644 --- a/web/style.css +++ b/web/style.css @@ -146,6 +146,21 @@ h2 { text-align: center; } +.upload-hint { + margin: 15px 0; + padding: 12px; + background: rgba(255, 165, 0, 0.15); + border: 1px solid rgba(255, 165, 0, 0.5); + border-radius: 8px; + color: #ffa500; + font-size: 0.9rem; + display: none; +} + +.upload-hint.visible { + display: block; +} + .file-input-wrapper { margin-bottom: 20px; } -- 2.54.0 From 9ce171911d794355733970086f53b70d0c6b82e3 Mon Sep 17 00:00:00 2001 From: TolaMironcenko Date: Sat, 14 Mar 2026 07:15:04 +0700 Subject: [PATCH 3/5] added versioning --- README.md | 160 +++++++++++++++++++++++++++++++++++------------ src/main.c | 165 +++++++++++++++++++++++++++++++++++++++++++++++-- web/app.js | 69 ++++++++++++++++++--- web/index.html | 36 +++++++++++ web/style.css | 35 +++++++++++ 5 files changed, 413 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 8d47ac8..f4815ca 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Bootloader Update Manager -Веб-приложение на C для обновления ядра, device-tree и U-Boot environment на Orange Pi Zero 2W. +Веб-приложение на C для обновления ядра, device-tree и U-Boot environment на Orange Pi Zero 2W (Allwinner H618). ## Возможности @@ -10,30 +10,46 @@ - **U-Boot Environment**: Загрузка `uboot.env` - **U-Boot Binary**: Загрузка `u-boot-sunxi-with-spl.bin` с записью в: - `/media/u-boot-sunxi-with-spl.bin` (файл) - - `/dev/mtd0` (SPI NOR flash) + - `/dev/mtd0` (SPI NOR flash, с предварительным стиранием) - `/dev/mmcblk1` со смещением 8KB (SD карта, аналог `dd seek=8`) - **Редактор extlinux.conf**: Просмотр и редактирование конфигурации загрузчика - **Индикатор прогресса**: Отображение прогресса загрузки в реальном времени - **Авто-монтирование**: Автоматически монтирует `/dev/mmcblk1p1` в `/media` -- **Поддержка больших файлов**: Загрузка файлов до 200 MB (до 1 MB для U-Boot Binary) -- **Перезагрузка системы**: Кнопка безопасной перезагрузки с корректным отмонтированием -- **Мониторинг температуры**: Отображение температуры CPU с цветовой индикацией +- **Поддержка больших файлов**: + - До 200 MB для обычных файлов + - До 1 MB для U-Boot Binary (безопасный лимит) +- **Перезагрузка системы**: Кнопка безопасной перезагрузки: + - `sync()` - сброс буферов ФС + - `umount /media` - отмонтирование + - `shutdown -r now` - мягкая перезагрузка через finit +- **Мониторинг температуры**: Отображение температуры CPU с цветовой индикацией: + - < 50°C: 🟢 Зелёный (холодно) + - < 70°C: 🔵 Синий (норма) + - < 80°C: 🟠 Оранжевый (тепло) + - ≥ 80°C: 🔴 Красный (горячо) +- **Отслеживание версий файлов**: + - Автоматическое сохранение даты/времени загрузки + - Файлы `.version` для каждого загруженного файла + - Timestamp передаётся от браузера (не зависит от времени на плате) + - Человекочитаемый формат: `ГГГГ-ММ-ДД ЧЧ:ММ:СС` + - Отображение последней даты модификации в веб-интерфейсе ## Структура проекта ``` . -├── main.c # Основной сервер (C + mongoose + cJSON) -├── index.html # HTML веб-интерфейса -├── style.css # Стили веб-интерфейса -├── app.js # Frontend JavaScript -├── Makefile # Конфигурация сборки -├── README.md # Документация -├── mongoose.h # Mongoose web server (v7.19) -├── mongoose.c # Mongoose web server (v7.19) -├── cJSON.h # JSON parser (v1.7.18) -├── cJSON.c # JSON parser (v1.7.18) -└── mongoose_config.h # Конфигурация mongoose +├── src/ +│ ├── main.c # Основной сервер (C + mongoose + cJSON) +│ └── lib/ +│ ├── mongoose.c/h # Mongoose web server (v7.19) +│ └── cJSON.c/h # JSON parser (v1.7.18) +├── web/ +│ ├── index.html # HTML веб-интерфейса +│ ├── style.css # Стили веб-интерфейса +│ └── app.js # Frontend JavaScript +├── mongoose_config.h # Конфигурация mongoose +├── Makefile # Конфигурация сборки +└── README.md # Документация ``` ## Требования @@ -62,53 +78,72 @@ sudo ./up ### System Status - Статус монтирования `/dev/mmcblk1p1` → `/media` -- Информация о устройстве +- Температура CPU (обновление каждые 5 секунд) + +### File Versions +Отображение даты последней загрузки/изменения для файлов: +- Kernel (Image.gz) +- Device-Tree (DTB) +- U-Boot Env +- U-Boot Binary +- Extlinux (обновляется при сохранении через редактор) + +**Формат даты**: `2026-03-14 12:34:56` + +**Примечание**: Время берётся из браузера клиента, поэтому корректно даже если на плате не настроено системное время. ### File Upload -Три вкладки для загрузки: +Четыре вкладки для загрузки: - **Kernel** → `/media/Image.gz` - **Device-Tree** → `/media/sun50i-h618-orangepi-zero2w.dtb` - **U-Boot Environment** → `/media/uboot.env` +- **U-Boot Binary (SPL)** → `/media/u-boot-sunxi-with-spl.bin` + `/dev/mtd0` + `/dev/mmcblk1` (+8KB) + +При загрузке браузер автоматически отправляет текущий timestamp, который сохраняется в `.version` файл. ### extlinux.conf Editor - Загрузка текущего `extlinux.conf` - Редактирование в текстовом поле -- Сохранение изменений +- Сохранение изменений (с записью версии) +- Очистка редактора ### System Control - Кнопка **Reboot System** с подтверждением -- Безопасная перезагрузка: - - `sync()` - сброс буферов ФС - - `umount /media` - отмонтирование - - `shutdown -r now` - мягкая перезагрузка через finit +- Безопасная перезагрузка через `shutdown -r now` ## API Endpoints | Endpoint | Method | Описание | |----------|--------|----------| -| `/api/upload` | POST | Загрузка файлов (kernel, dtb, uboot) | -| `/api/extlinux` | GET | Чтение extlinux.conf | -| `/api/extlinux` | POST | Запись extlinux.conf | +| `/api/upload?timestamp=...` | POST | Загрузка файлов (kernel, dtb, uboot, uboot_bin) | +| `/api/extlinux?timestamp=...` | GET | Чтение extlinux.conf | +| `/api/extlinux?timestamp=...` | POST | Запись extlinux.conf (с версией) | | `/api/progress` | GET | Статус загрузки | | `/api/mount` | GET | Статус монтирования | | `/api/temperature` | GET | Температура CPU | +| `/api/filedate?file=...` | GET | Дата версии файла | | `/api/reboot` | POST | Перезагрузка системы | ## Параметры загрузки -Для `/api/upload` (multipart/form-data): -- `type`: Тип файла (`kernel`, `dtb`, `uboot`) +Для `/api/upload`: +- `type`: Тип файла (`kernel`, `dtb`, `uboot`, `uboot_bin`) - `file`: Загружаемый файл +- `timestamp`: ISO 8601 timestamp (отправляется браузером автоматически) + +Для `/api/extlinux` (POST): +- `content`: Содержимое файла extlinux.conf +- `timestamp`: ISO 8601 timestamp (отправляется браузером автоматически) ## Расположение файлов -| Тип | Файл назначения | -|-----|-----------------| -| Ядро | `/media/Image.gz` | -| Device-Tree | `/media/sun50i-h618-orangepi-zero2w.dtb` | -| U-Boot Env | `/media/uboot.env` | -| U-Boot Binary | `/media/u-boot-sunxi-with-spl.bin` + `/dev/mtd0` + `/dev/mmcblk1` (+8KB offset) | -| Extlinux | `/media/extlinux/extlinux.conf` | +| Тип | Файл назначения | Версия | +|-----|-----------------|--------| +| Ядро | `/media/Image.gz` | `/media/Image.gz.version` | +| Device-Tree | `/media/sun50i-h618-orangepi-zero2w.dtb` | `/media/sun50i-h618-orangepi-zero2w.dtb.version` | +| U-Boot Env | `/media/uboot.env` | `/media/uboot.env.version` | +| U-Boot Binary | `/media/u-boot-sunxi-with-spl.bin` + `/dev/mtd0` + `/dev/mmcblk1` (+8KB) | `/media/u-boot-sunxi-with-spl.bin.version` | +| Extlinux | `/media/extlinux/extlinux.conf` | `/media/extlinux.conf.version` | ## Конфигурация @@ -119,21 +154,66 @@ sudo ./up ``` ### Ограничения -- Максимальный размер файла: **200 MB** +- Максимальный размер файла: **200 MB** (обычные файлы) +- Максимальный размер U-Boot: **1 MB** (безопасно для boot area) - Порт сервера: **43243** +## Детали реализации + +### U-Boot Binary загрузка +1. Сохранение файла в `/media/u-boot-sunxi-with-spl.bin` +2. Получение информации о SPI NOR через `ioctl(MEMGETINFO)` +3. Стирание SPI NOR через `ioctl(MEMERASE)` +4. Запись в `/dev/mtd0` (SPI NOR flash) +5. Запись в `/dev/mmcblk1` со смещением 8KB (сектор 16) +6. `sync()` для гарантии записи + +**Прогресс бар**: +- 0-30%: Запись файла в `/media` +- 30-65%: Запись в SPI NOR +- 65-100%: Запись в SD карту + +### Temperature monitoring +- Чтение из `/sys/class/thermal/thermal_zone*` (0-3) +- Приоритет: зоны с типом `cpu`, `soc` +- Резервный источник: `/sys/class/hwmon/hwmon*/temp1_input` (0-3) +- Приоритет: сенсоры с именем `cpu`, `sunxi`, `acpitz` +- Обновление каждые 5 секунд + +### File version tracking +1. Браузер создаёт ISO 8601 timestamp: `2026-03-14T12:34:56.789Z` +2. Timestamp кодируется для URL: `2026-03-14T12%3A34%3A56.789Z` +3. Сервер декодирует `%3A` → `:` +4. Конвертирует в человекочитаемый формат: `2026-03-14 12:34:56` +5. Сохраняет в `.version` +6. Frontend опрашивает `/api/filedate` каждые 10 секунд + +**Важно**: Время берётся из браузера клиента, поэтому: +- ✅ Корректное время даже если на плате не настроено +- ✅ Часовой пояс клиента +- ✅ Синхронизация через NTP браузера + +### Extlinux.conf editor +- Сохранение через POST с timestamp от браузера +- Автоматическое обновление списка версий после сохранения +- Версия записывается в `/media/extlinux.conf.version` + ## Безопасность ⚠️ **Предупреждение**: -- Приложение требует права **root** для монтирования и перезагрузки +- Приложение требует права **root** для: + - Монтирования устройств (`mount`, `umount`) + - Записи в `/dev/mtd0` (SPI NOR flash) + - Записи в `/dev/mmcblk1` (SD карта) + - Перезагрузки системы (`shutdown`) - Используйте с осторожностью в production окружении -- Рекомендуется ограничить доступ к веб-интерфейсу +- Рекомендуется ограничить доступ к веб-интерфейсу (firewall, auth) ## Зависимости Все зависимости встроены в проект: -- `mongoose.c/h` - веб-сервер (из репозитория Cesanta) -- `cJSON.c/h` - JSON парсер (из репозитория DaveGamble) +- `mongoose.c/h` - веб-сервер (из репозитория Cesanta, v7.19) +- `cJSON.c/h` - JSON парсер (из репозитория DaveGamble, v1.7.18) ## Лицензия diff --git a/src/main.c b/src/main.c index 08d31cf..b0846c2 100644 --- a/src/main.c +++ b/src/main.c @@ -344,6 +344,25 @@ static void handle_extlinux_write(struct mg_connection *c, struct mg_http_messag return; } + // Get timestamp from query string (sent by browser) + char timestamp[64] = {0}; + if (hm->query.len > 0) { + char query[512] = {0}; + memcpy(query, hm->query.buf, hm->query.len < sizeof(query) ? hm->query.len : sizeof(query)-1); + struct mg_str ts_name = mg_str("timestamp"); + struct mg_str ts_value = mg_http_var(mg_str(query), ts_name); + if (ts_value.len > 0) { + mg_str_cpy(ts_value, timestamp, sizeof(timestamp)); + // URL-decode timestamp (replace %3A with :) + for (size_t i = 0; i < strlen(timestamp) - 2; i++) { + if (timestamp[i] == '%' && timestamp[i+1] == '3' && timestamp[i+2] == 'A') { + timestamp[i] = ':'; + memmove(×tamp[i+1], ×tamp[i+3], strlen(×tamp[i+3]) + 1); + } + } + } + } + if (mount_device() < 0) { cJSON_Delete(root); mg_http_reply(c, 500, "", "{\"error\":\"Failed to mount device\"}\n"); @@ -362,6 +381,31 @@ static void handle_extlinux_write(struct mg_connection *c, struct mg_http_messag fprintf(f, "%s", content->valuestring); fclose(f); + + // Write version file with timestamp from browser + if (strlen(timestamp) > 0) { + char version_path[512]; + snprintf(version_path, sizeof(version_path), "%s/%s.version", MOUNT_POINT, "extlinux.conf"); + FILE *vf = fopen(version_path, "w"); + if (vf) { + // Convert ISO 8601 to human-readable format + // Input: 2026-03-14T12:34:56.789Z + // Output: 2026-03-14 12:34:56 + char readable_date[32] = {0}; + if (strlen(timestamp) >= 19) { + strncpy(readable_date, timestamp, 10); // YYYY-MM-DD + readable_date[10] = ' '; + strncpy(readable_date + 11, timestamp + 11, 8); // HH:MM:SS + readable_date[19] = '\0'; + } else { + strncpy(readable_date, timestamp, sizeof(readable_date) - 1); + } + fprintf(vf, "%s\n", readable_date); + fclose(vf); + printf("Written extlinux.conf version: %s\n", readable_date); + } + } + sync(); mg_http_reply(c, 200, "Content-Type: application/json\r\n", @@ -431,10 +475,10 @@ static void handle_temperature(struct mg_connection *c) { char type[32] = {0}; if (fscanf(type_f, "%31s", type) == 1) { // Prefer CPU or soc zones - int is_cpu = (strstr(type, "cpu") != NULL || + int is_cpu = (strstr(type, "cpu") != NULL || strstr(type, "soc") != NULL || strstr(type, "CPU") != NULL); - + int temp_raw; if (fscanf(f, "%d", &temp_raw) == 1) { temperature = temp_raw / 1000.0f; @@ -467,11 +511,11 @@ static void handle_temperature(struct mg_connection *c) { char name[32] = {0}; if (fscanf(name_f, "%31s", name) == 1) { // Prefer CPU or sunxi sensors - int is_cpu = (strstr(name, "cpu") != NULL || + int is_cpu = (strstr(name, "cpu") != NULL || strstr(name, "sunxi") != NULL || strstr(name, "CPU") != NULL || strstr(name, "acpitz") != NULL); - + int temp_raw; if (fscanf(f, "%d", &temp_raw) == 1) { temperature = temp_raw / 1000.0f; @@ -499,7 +543,7 @@ static void handle_temperature(struct mg_connection *c) { "/sys/class/hwmon/hwmon3/temp1_input", NULL }; - + for (int i = 0; fallback_paths[i] != NULL && !success; i++) { FILE *f = fopen(fallback_paths[i], "r"); if (f) { @@ -524,6 +568,72 @@ static void handle_temperature(struct mg_connection *c) { cJSON_Delete(root); } +// Handle file date read +static void handle_filedate(struct mg_connection *c, struct mg_http_message *hm) { + // Get filename from query string + char filename[128] = {0}; + char query[256] = {0}; + + if (hm->query.len > 0 && hm->query.len < sizeof(query)) { + memcpy(query, hm->query.buf, hm->query.len); + query[hm->query.len] = '\0'; + + // Parse query string: file=Image.gz + struct mg_str name = mg_str("file"); + struct mg_str value = mg_http_var(mg_str(query), name); + if (value.len > 0 && value.len < sizeof(filename)) { + mg_str_cpy(value, filename, sizeof(filename)); + } + } + + if (strlen(filename) == 0) { + mg_http_reply(c, 400, "", "{\"error\":\"Missing file parameter\"}\n"); + return; + } + + // Build version file path + char version_path[512]; + snprintf(version_path, sizeof(version_path), "%s/%s.version", MOUNT_POINT, filename); + + // Try to read version file + FILE *f = fopen(version_path, "r"); + if (!f) { + // No version file, return empty date + cJSON *root = cJSON_CreateObject(); + cJSON_AddStringToObject(root, "status", "success"); + cJSON_AddStringToObject(root, "date", ""); + cJSON_AddStringToObject(root, "filename", filename); + + char *json = cJSON_PrintUnformatted(root); + mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s\n", json); + free(json); + cJSON_Delete(root); + return; + } + + char date_str[64] = {0}; + if (fgets(date_str, sizeof(date_str), f) == NULL) { + date_str[0] = '\0'; + } + fclose(f); + + // Remove newline + size_t len = strlen(date_str); + if (len > 0 && date_str[len-1] == '\n') { + date_str[len-1] = '\0'; + } + + cJSON *root = cJSON_CreateObject(); + cJSON_AddStringToObject(root, "status", "success"); + cJSON_AddStringToObject(root, "date", date_str); + cJSON_AddStringToObject(root, "filename", filename); + + char *json = cJSON_PrintUnformatted(root); + mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s\n", json); + free(json); + cJSON_Delete(root); +} + // Handle system reboot static void handle_reboot(struct mg_connection *c) { cJSON *root = cJSON_CreateObject(); @@ -619,6 +729,25 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) { g_upload.progress = 0; snprintf(g_upload.status, sizeof(g_upload.status), "Uploading..."); + // Get timestamp from query string (sent by browser) + char timestamp[64] = {0}; + if (hm->query.len > 0) { + char query[512] = {0}; + memcpy(query, hm->query.buf, hm->query.len < sizeof(query) ? hm->query.len : sizeof(query)-1); + struct mg_str ts_name = mg_str("timestamp"); + struct mg_str ts_value = mg_http_var(mg_str(query), ts_name); + if (ts_value.len > 0) { + mg_str_cpy(ts_value, timestamp, sizeof(timestamp)); + // URL-decode timestamp (replace %3A with :) + for (size_t i = 0; i < strlen(timestamp) - 2; i++) { + if (timestamp[i] == '%' && timestamp[i+1] == '3' && timestamp[i+2] == 'A') { + timestamp[i] = ':'; + memmove(×tamp[i+1], ×tamp[i+3], strlen(×tamp[i+3]) + 1); + } + } + } + } + // Special handling for U-Boot binary if (strcmp(type, "uboot_bin") == 0) { if (write_uboot_to_devices(file_data.buf, file_data.len, filepath) < 0) { @@ -653,6 +782,30 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) { g_upload.progress = 100; } + // Write version file with timestamp + if (strlen(timestamp) > 0) { + char version_path[512]; + snprintf(version_path, sizeof(version_path), "%s/%s.version", MOUNT_POINT, filename); + FILE *vf = fopen(version_path, "w"); + if (vf) { + // Convert ISO 8601 to human-readable format + // Input: 2026-03-14T12:34:56.789Z + // Output: 2026-03-14 12:34:56 + char readable_date[32] = {0}; + if (strlen(timestamp) >= 19) { + strncpy(readable_date, timestamp, 10); // YYYY-MM-DD + readable_date[10] = ' '; + strncpy(readable_date + 11, timestamp + 11, 8); // HH:MM:SS + readable_date[19] = '\0'; + } else { + strncpy(readable_date, timestamp, sizeof(readable_date) - 1); + } + fprintf(vf, "%s\n", readable_date); + fclose(vf); + printf("Written version file: %s (%s)\n", version_path, readable_date); + } + } + printf("Upload complete: %s\n", filepath); g_upload.received = file_data.len; @@ -683,6 +836,8 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) { handle_mount_status(c); } else if (mg_match_uri(hm->uri, "/api/temperature")) { handle_temperature(c); + } else if (mg_match_uri(hm->uri, "/api/filedate")) { + handle_filedate(c, hm); } else if (mg_match_uri(hm->uri, "/api/reboot")) { handle_reboot(c); } else if (mg_match_uri(hm->uri, "/")) { diff --git a/web/app.js b/web/app.js index 195f098..adda793 100644 --- a/web/app.js +++ b/web/app.js @@ -23,8 +23,10 @@ document.addEventListener("DOMContentLoaded", function () { // Initialize checkMountStatus(); checkTemperature(); + checkFileDates(); setInterval(checkMountStatus, 10000); // Check every 10 seconds setInterval(checkTemperature, 5000); // Check temperature every 5 seconds + setInterval(checkFileDates, 10000); // Check file dates every 10 seconds // Tab switching tabBtns.forEach((btn) => { @@ -114,6 +116,9 @@ document.addEventListener("DOMContentLoaded", function () { progressBytes.textContent = ""; hideResult(); + // Get current timestamp + const timestamp = new Date().toISOString(); + try { const xhr = new XMLHttpRequest(); @@ -135,6 +140,8 @@ document.addEventListener("DOMContentLoaded", function () { `Successfully uploaded ${response.filename} (${formatBytes(response.size)})`, "success", ); + // Refresh file dates after upload + checkFileDates(); } else { let errorMsg = "Upload failed"; try { @@ -151,7 +158,10 @@ document.addEventListener("DOMContentLoaded", function () { showResult("Network error", "error"); }); - xhr.open("POST", "/api/upload"); + xhr.open( + "POST", + `/api/upload?timestamp=${encodeURIComponent(timestamp)}`, + ); xhr.send(formData); } catch (error) { progressStatus.textContent = "Failed"; @@ -227,19 +237,27 @@ document.addEventListener("DOMContentLoaded", function () { showEditorStatus("Saving...", ""); + // Get current timestamp from browser + const timestamp = new Date().toISOString(); + try { - const response = await fetch("/api/extlinux", { - method: "POST", - headers: { - "Content-Type": "application/json", + const response = await fetch( + `/api/extlinux?timestamp=${encodeURIComponent(timestamp)}`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ content: content }), }, - body: JSON.stringify({ content: content }), - }); + ); const data = await response.json(); if (data.status === "success") { showEditorStatus("Successfully saved extlinux.conf", "success"); + // Refresh file dates after save + checkFileDates(); } else { showEditorStatus(data.error || "Failed to save", "error"); } @@ -349,4 +367,41 @@ document.addEventListener("DOMContentLoaded", function () { if (temp < 80) return "#ffa500"; // Orange - warm return "#ff4757"; // Red - hot } + + // File dates functions + const fileDates = { + "Image.gz": document.getElementById("date-Image.gz"), + "sun50i-h618-orangepi-zero2w.dtb": document.getElementById( + "date-sun50i-h618-orangepi-zero2w.dtb", + ), + "uboot.env": document.getElementById("date-uboot.env"), + "u-boot-sunxi-with-spl.bin": document.getElementById( + "date-u-boot-sunxi-with-spl.bin", + ), + "extlinux.conf": document.getElementById("date-extlinux.conf"), + }; + + async function checkFileDates() { + for (const [filename, element] of Object.entries(fileDates)) { + if (!element) continue; + + try { + const response = await fetch( + `/api/filedate?file=${encodeURIComponent(filename)}`, + ); + const data = await response.json(); + + if (data.status === "success" && data.date) { + element.textContent = data.date; + element.style.color = "#00ff88"; + } else { + element.textContent = "Not available"; + element.style.color = "#888"; + } + } catch (error) { + element.textContent = "Error"; + element.style.color = "#ff4757"; + } + } + } }); diff --git a/web/index.html b/web/index.html index 623e9a3..83d8ea7 100644 --- a/web/index.html +++ b/web/index.html @@ -44,6 +44,42 @@ +
+

File Versions

+
+
+ Kernel (Image.gz): + -- +
+
+ Device-Tree (DTB): + -- +
+
+ U-Boot Env: + -- +
+
+ U-Boot Binary: + -- +
+
+ Extlinux: + -- +
+
+
+

File Upload

diff --git a/web/style.css b/web/style.css index eecd3e2..fbe8f27 100644 --- a/web/style.css +++ b/web/style.css @@ -113,6 +113,41 @@ h2 { font-size: 0.9rem; } +/* File Versions Section */ +.files-section { + border: 1px solid rgba(0, 217, 255, 0.3); +} + +.files-section h2 { + color: #00d9ff; +} + +.files-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 12px; +} + +.file-item { + background: rgba(0, 0, 0, 0.2); + padding: 12px 15px; + border-radius: 8px; + display: flex; + flex-direction: column; + gap: 5px; +} + +.file-label { + color: #888; + font-size: 0.85rem; +} + +.file-date { + color: #00ff88; + font-weight: 600; + font-size: 0.95rem; +} + /* Upload Section */ .upload-tabs { display: flex; -- 2.54.0 From fcd7e6b45ed0b91d74d42b408c9bff0000c45d3d Mon Sep 17 00:00:00 2001 From: wt Date: Thu, 17 Sep 2026 16:10:44 +0700 Subject: [PATCH 4/5] updated ci/cd --- .gitea/workflows/actions.yaml | 40 ----------------------------------- .gitea/workflows/cmake.yaml | 16 ++++++++++++++ .gitea/workflows/make.yaml | 12 +++++++++++ .gitea/workflows/meson.yaml | 16 ++++++++++++++ 4 files changed, 44 insertions(+), 40 deletions(-) delete mode 100644 .gitea/workflows/actions.yaml create mode 100644 .gitea/workflows/cmake.yaml create mode 100644 .gitea/workflows/make.yaml create mode 100644 .gitea/workflows/meson.yaml diff --git a/.gitea/workflows/actions.yaml b/.gitea/workflows/actions.yaml deleted file mode 100644 index ff5a098..0000000 --- a/.gitea/workflows/actions.yaml +++ /dev/null @@ -1,40 +0,0 @@ -name: Prepare Build and Testing executable binary -run-name: ${{ gitea.actor }} -on: [push] - -jobs: - Prepare-Build-Testing-With-Make: - runs-on: ubuntu-latest - steps: - - name: Check out repository code - uses: actions/checkout@v3 - - name: Building binary with make - run: make -j$(nproc) - - Prepare-Build-Testing-With-CMake: - runs-on: ubuntu-latest - steps: - - name: prepare-container - run: | - apt install -y cmake - - name: Check out repository code - uses: actions/checkout@v3 - - name: Building binary with CMake - run: | - mkdir build && \ - cd build && \ - cmake .. && \ - make -j$(nproc) - - Prepare-Build-Testing-With-Meson: - runs-on: ubuntu-latest - steps: - - name: prepare-container - run: | - apt install -y meson - - name: Check out repository code - uses: actions/checkout@v3 - - name: Building binary with meson - run: | - meson setup build && \ - ninja -C build diff --git a/.gitea/workflows/cmake.yaml b/.gitea/workflows/cmake.yaml new file mode 100644 index 0000000..bb95b98 --- /dev/null +++ b/.gitea/workflows/cmake.yaml @@ -0,0 +1,16 @@ +name: Prepare Build with CMake and Testing executable binary +run-name: ${{ gitea.actor }} +on: [push] + +jobs: + Prepare-Build-Testing-With-CMake: + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v4 + - name: Install build tools + run: apt install -y cmake + - name: Configure CMake + run: cmake -B ${{gitea.workspace}}/build + - name: Build project + run: cmake --build ${{gitea.workspace}}/build diff --git a/.gitea/workflows/make.yaml b/.gitea/workflows/make.yaml new file mode 100644 index 0000000..8496d9b --- /dev/null +++ b/.gitea/workflows/make.yaml @@ -0,0 +1,12 @@ +name: Prepare Build with Make and Testing executable binary +run-name: ${{ gitea.actor }} +on: [push] + +jobs: + Prepare-Build-Testing-With-Make: + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v3 + - name: Building binary with make + run: make -j$(nproc) diff --git a/.gitea/workflows/meson.yaml b/.gitea/workflows/meson.yaml new file mode 100644 index 0000000..bad2bf3 --- /dev/null +++ b/.gitea/workflows/meson.yaml @@ -0,0 +1,16 @@ +name: Prepare Build with Meson and Testing executable binary +run-name: ${{ gitea.actor }} +on: [push] + +jobs: + Prepare-Build-Testing-With-Meson: + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v4 + - name: Install build tools + run: apt install -y meson + - name: Configure Project + run: meson setup build --buildtype=release + - name: Build Project + run: meson compile -C build -- 2.54.0 From 1b6cacd1c49119434527ba78842471f0ef052156 Mon Sep 17 00:00:00 2001 From: wt Date: Thu, 17 Sep 2026 16:13:06 +0700 Subject: [PATCH 5/5] fix update ci/cd --- .gitea/workflows/cmake.yaml | 2 +- .gitea/workflows/meson.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/cmake.yaml b/.gitea/workflows/cmake.yaml index bb95b98..a9e3930 100644 --- a/.gitea/workflows/cmake.yaml +++ b/.gitea/workflows/cmake.yaml @@ -9,7 +9,7 @@ jobs: - name: Check out repository code uses: actions/checkout@v4 - name: Install build tools - run: apt install -y cmake + run: apt update && apt install -y cmake - name: Configure CMake run: cmake -B ${{gitea.workspace}}/build - name: Build project diff --git a/.gitea/workflows/meson.yaml b/.gitea/workflows/meson.yaml index bad2bf3..20702a6 100644 --- a/.gitea/workflows/meson.yaml +++ b/.gitea/workflows/meson.yaml @@ -9,7 +9,7 @@ jobs: - name: Check out repository code uses: actions/checkout@v4 - name: Install build tools - run: apt install -y meson + run: apt update && apt install -y meson - name: Configure Project run: meson setup build --buildtype=release - name: Build Project -- 2.54.0