Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions board/common/rootfs/etc/profile.d/update-check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
if [ -s /run/infix-update ]; then
printf '\n\033[1;33m *** %s ***\033[0m\n\n' "$(cat /run/infix-update)"
fi
1 change: 1 addition & 0 deletions board/common/rootfs/etc/tmpfiles.d/infix-schedule.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
f /run/infix-update 0666 admin admin
56 changes: 56 additions & 0 deletions board/common/rootfs/usr/sbin/infix-check-update
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/bin/sh
# Check for available firmware updates and notify on login if one exists.
# Called by the scheduler when predefined-action infix-schedule:check-update fires.

NOTIFY_FILE=/run/infix-update
TAG=infix-update

# Source os-release for VERSION and IMAGE_ID
if [ ! -f /etc/os-release ]; then
logger -t "$TAG" "ERROR: /etc/os-release not found"
exit 1
fi
. /etc/os-release

# Dev/dirty builds have no comparable semver — always show the latest release
IS_RELEASE=true
if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then
IS_RELEASE=false
fi

# Read configured update-url from running config, fall back to upstream
UPDATE_URL=$(copy running-config \
-x '/ietf-system:system/infix-system:software/check-update/update-url' \
2>/dev/null \
| jq -r '.. | objects | ."update-url"? // empty')
UPDATE_URL=${UPDATE_URL:-"https://github.com/kernelkit/infix"}

# Derive API URL from the configured update URL.
# Default (github.com): https://github.com/org/repo → https://api.github.com/repos/org/repo
REPO=$(echo "$UPDATE_URL" | sed 's|https://github.com/||; s|/*$||')
API_URL="https://api.github.com/repos/${REPO}/releases/latest"

LATEST_TAG=$(curl -sSL --max-time 10 "$API_URL" 2>/dev/null \
| sed -n 's/.*"tag_name" *: *"\([^"]*\)".*/\1/p' \
| head -1)
if [ -z "$LATEST_TAG" ]; then
logger -p daemon.info -t "$TAG" "Update check skipped: could not reach ${API_URL}"
exit 0
fi
LATEST=${LATEST_TAG#v}

# Compare: is $1 strictly newer than $2?
newer() {
[ "$1" = "$2" ] && return 1
[ "$(printf '%s\n%s' "$1" "$2" | sort -V | tail -1)" = "$1" ]
}

if [ "$IS_RELEASE" = false ] || newer "$LATEST" "$VERSION"; then
BUNDLE_URL="${UPDATE_URL}/releases/download/${LATEST_TAG}/${IMAGE_ID}-${LATEST}.tar.gz"
MSG="Firmware update available: ${LATEST_TAG}, running ${VERSION} (see ${BUNDLE_URL})"
logger -t "$TAG" "$MSG — ${BUNDLE_URL}"
printf '%s\n' "$MSG" > "$NOTIFY_FILE"
else
logger -p daemon.debug -t "$TAG" "No update available (current: $VERSION, latest: $LATEST)"
printf '' > "$NOTIFY_FILE"
fi
10 changes: 10 additions & 0 deletions doc/ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@ Change Log

All notable changes to the project are documented in this file.

[v26.06.0][] - [[UNRELEASED]]
-------------------------

### Changes

- Added ietf-schedule (basic recurennce) implementation (RFC9922)

### Fixes


[v26.05.0][] - 2026-05-29
-------------------------

Expand Down
2 changes: 1 addition & 1 deletion package/confd/confd.mk
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ else
CONFD_CONF_OPTS += --disable-gps
endif
define CONFD_INSTALL_EXTRA
for fn in confd.conf resolvconf.conf; do \
for fn in confd.conf crond.conf resolvconf.conf; do \
cp $(CONFD_PKGDIR)/$$fn $(FINIT_D)/available/; \
ln -sf ../available/$$fn $(FINIT_D)/enabled/$$fn; \
done
Expand Down
2 changes: 2 additions & 0 deletions package/confd/crond.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Cron daemon for infix-schedule
service [2345] crond -f -- Cron daemon
1 change: 1 addition & 0 deletions src/confd/src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ confd_plugin_la_SOURCES = \
if-wireguard.c \
keystore.c \
system.c \
schedule.c \
ntp.c \
ptp.c \
syslog.c \
Expand Down
14 changes: 14 additions & 0 deletions src/confd/src/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,10 @@ static int change_cb(sr_session_ctx_t *session, uint32_t sub_id, const char *mod
if ((rc = system_change(session, config, diff, event, confd)))
goto free_diff;

/* infix-schedule */
if ((rc = schedule_change(session, config, diff, event, confd)))
goto free_diff;

/* infix-containers */
#ifdef CONTAINERS
if ((rc = containers_change(session, config, diff, event, confd)))
Expand Down Expand Up @@ -794,6 +798,11 @@ int sr_plugin_init_cb(sr_session_ctx_t *session, void **priv)
ERROR("Failed to subscribe to ietf-hardware");
goto err;
}
rc = subscribe_model("infix-schedule", &confd, 0);
if (rc) {
ERROR("Failed to subscribe to infix-schedule");
goto err;
}
rc = subscribe_model("infix-firewall", &confd, 0);
if (rc) {
ERROR("Failed to subscribe to infix-firewall");
Expand Down Expand Up @@ -858,6 +867,11 @@ int sr_plugin_init_cb(sr_session_ctx_t *session, void **priv)
rc = ntp_candidate_init(&confd);
if (rc)
goto err;

rc = schedule_init(&confd);
if (rc)
goto err;

/* YOUR_INIT GOES HERE */

return SR_ERR_OK;
Expand Down
4 changes: 4 additions & 0 deletions src/confd/src/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ int system_rpc_init (struct confd *confd);
int hostnamefmt (struct confd *confd, const char *fmt, char *hostnm, size_t hostlen, char *domain, size_t domlen);
int system_change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd);

/* schedule.c */
int schedule_init(struct confd *confd);
int schedule_change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd);

/* containers.c */
#ifdef CONTAINERS
int containers_change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd);
Expand Down
242 changes: 242 additions & 0 deletions src/confd/src/schedule.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
/* SPDX-License-Identifier: BSD-3-Clause */

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

#include <libite/lite.h>
#include <srx/common.h>
#include <srx/lyx.h>
#include <srx/srx_val.h>
#include "core.h"

#define XPATH_BASE "/ietf-system:system/infix-schedule:schedules"
#define CRONTAB_FILE "/var/spool/cron/crontabs/admin"

static const char *action_to_cmd(const char *action)
{
if (!action)
return NULL;
if (strstr(action, "reboot"))
return "/usr/sbin/reboot";
if (strstr(action, "check-update"))
return "/usr/sbin/infix-check-update";
return NULL;
}

/*
* Convert ietf-schedule recurrence to a 5-field cron expression.
*
* Frequency mapping:
* minutely/N → *\/N * * * *
* hourly/N → 0 *\/N * * *
* daily/N → 0 0 *\/N * *
* weekly/N → 0 0 * * *\/N
* monthly/N → 0 0 1 *\/N *
*
* Optional by-* leaves refine the expression:
* byminute → replaces the minute field
* byhour → replaces the hour field
* byday → replaces the day-of-week field
* bymonthday → replaces the day-of-month field (positive values only, 1-31)
* byyearmonth → replaces the month field
*/
static void build_cron_expr(struct lyd_node *recurrence, char *expr, size_t sz)
{
const char *freq, *ivstr;
struct lyd_node *node;
char min[64], hr[64], dom[64], mon[64], dow[64];
int iv, first;

snprintf(min, sizeof(min), "*");
snprintf(hr, sizeof(hr), "*");
snprintf(dom, sizeof(dom), "*");
snprintf(mon, sizeof(mon), "*");
snprintf(dow, sizeof(dow), "*");

freq = lydx_get_cattr(recurrence, "frequency");
ivstr = lydx_get_cattr(recurrence, "interval");
if (!freq || !ivstr)
goto done;

iv = atoi(ivstr);
if (iv <= 0)
iv = 1;

if (strstr(freq, "minutely")) {
if (iv == 1)
snprintf(min, sizeof(min), "*");
else
snprintf(min, sizeof(min), "*/%d", iv);
} else if (strstr(freq, "hourly")) {
snprintf(min, sizeof(min), "0");
if (iv == 1)
snprintf(hr, sizeof(hr), "*");
else
snprintf(hr, sizeof(hr), "*/%d", iv);
} else if (strstr(freq, "daily")) {
snprintf(min, sizeof(min), "0");
snprintf(hr, sizeof(hr), "0");
if (iv > 1)
snprintf(dom, sizeof(dom), "*/%d", iv);
} else if (strstr(freq, "weekly")) {
snprintf(min, sizeof(min), "0");
snprintf(hr, sizeof(hr), "0");
if (iv == 1)
snprintf(dow, sizeof(dow), "*");
else
snprintf(dow, sizeof(dow), "*/%d", iv);
} else if (strstr(freq, "monthly")) {
snprintf(min, sizeof(min), "0");
snprintf(hr, sizeof(hr), "0");
snprintf(dom, sizeof(dom), "1");
if (iv > 1)
snprintf(mon, sizeof(mon), "*/%d", iv);
}

/* byminute: override minute field with explicit list */
first = 1;
LYX_LIST_FOR_EACH(lyd_child(recurrence), node, "byminute") {
const char *val = lyd_get_value(node);
if (!val) continue;
if (first) { snprintf(min, sizeof(min), "%s", val); first = 0; }
else strncat(min, ",", sizeof(min) - strlen(min) - 1),
strncat(min, val, sizeof(min) - strlen(min) - 1);
}

/* byhour: override hour field with explicit list */
first = 1;
LYX_LIST_FOR_EACH(lyd_child(recurrence), node, "byhour") {
const char *val = lyd_get_value(node);
if (!val) continue;
if (first) { snprintf(hr, sizeof(hr), "%s", val); first = 0; }
else strncat(hr, ",", sizeof(hr) - strlen(hr) - 1),
strncat(hr, val, sizeof(hr) - strlen(hr) - 1);
}

/* bymonthday: override day-of-month field */
first = 1;
LYX_LIST_FOR_EACH(lyd_child(recurrence), node, "bymonthday") {
const char *val = lyd_get_value(node);
if (!val) continue;
if (first) { snprintf(dom, sizeof(dom), "%s", val); first = 0; }
else strncat(dom, ",", sizeof(dom) - strlen(dom) - 1),
strncat(dom, val, sizeof(dom) - strlen(dom) - 1);
}

/* byyearmonth: override month field */
first = 1;
LYX_LIST_FOR_EACH(lyd_child(recurrence), node, "byyearmonth") {
const char *val = lyd_get_value(node);
if (!val) continue;
if (first) { snprintf(mon, sizeof(mon), "%s", val); first = 0; }
else strncat(mon, ",", sizeof(mon) - strlen(mon) - 1),
strncat(mon, val, sizeof(mon) - strlen(mon) - 1);
}

/* byday: override day-of-week field */
first = 1;
LYX_LIST_FOR_EACH(lyd_child(recurrence), node, "byday") {
const char *val = lydx_get_cattr(node, "weekday");
const char *num = NULL;
if (!val) continue;
/* map YANG weekday names to cron numbers (0=sunday) */
if (!strcmp(val, "sunday")) num = "0";
else if (!strcmp(val, "monday")) num = "1";
else if (!strcmp(val, "tuesday")) num = "2";
else if (!strcmp(val, "wednesday")) num = "3";
else if (!strcmp(val, "thursday")) num = "4";
else if (!strcmp(val, "friday")) num = "5";
else if (!strcmp(val, "saturday")) num = "6";
if (!num) continue;
if (first) { snprintf(dow, sizeof(dow), "%s", num); first = 0; }
else strncat(dow, ",", sizeof(dow) - strlen(dow) - 1),
strncat(dow, num, sizeof(dow) - strlen(dow) - 1);
}

done:
snprintf(expr, sz, "%s %s %s %s %s", min, hr, dom, mon, dow);
}

static void reload_crond(void)
{
char *args[] = { "pkill", "-HUP", "crond", NULL };

runbg(args, 0);
}

static void apply_schedules(struct lyd_node *config)
{
struct lyd_node *schedules, *sched;
FILE *fp;
int count = 0;

makepath("/var/spool/cron/crontabs");
fp = fopen(CRONTAB_FILE, "w");
if (!fp) {
ERROR("schedule: failed to open %s", CRONTAB_FILE);
return;
}
fprintf(fp, "# Managed by infix-schedule\n");

schedules = config ? lydx_get_xpathf(config, XPATH_BASE) : NULL;
if (!schedules)
goto out;

LYX_LIST_FOR_EACH(lyd_child(schedules), sched, "schedule") {
struct lyd_node *recurrence;
const char *name, *action, *cmd;
char expr[128];

if (!lydx_is_enabled(sched, "enabled"))
continue;

name = lydx_get_cattr(sched, "name");

recurrence = lydx_get_child(sched, "recurrence");
if (!recurrence)
continue;

build_cron_expr(recurrence, expr, sizeof(expr));

action = lydx_get_cattr(sched, "predefined-action");
cmd = action_to_cmd(action);
if (!cmd)
continue;

fprintf(fp, "# %s\n%s %s\n", name ?: "unnamed", expr, cmd);
NOTE("schedule: %s → cron '%s %s'", name ?: "unnamed", expr, cmd);
count++;
}

out:
fclose(fp);
reload_crond();
NOTE("schedule: %d active job(s) written to crontab", count);
}

int schedule_change(sr_session_ctx_t *session, struct lyd_node *config,
struct lyd_node *diff, sr_event_t event, struct confd *confd)
{
if (event != SR_EV_DONE)
return SR_ERR_OK;

apply_schedules(config);
return SR_ERR_OK;
}

int schedule_init(struct confd *confd)
{
sr_data_t *data = NULL;

/*
* Sync the crontab with current running config at startup so
* scheduled jobs survive across reboots.
*/
sr_get_data(confd->session, "//.", 0, 0, 0, &data);
apply_schedules(data ? data->tree : NULL);
if (data)
sr_release_data(data);

return SR_ERR_OK;
}
2 changes: 2 additions & 0 deletions src/confd/yang/confd.inc
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,6 @@ MODULES=(
"ieee1588-ptp-tt@2023-08-14.yang -e timestamp-correction"
"ieee802-dot1as-gptp@2025-12-10.yang"
"infix-ptp@2026-04-07.yang"
"ietf-schedule@2026-03-10.yang -e basic-recurrence"
"infix-schedule@2026-06-02.yang"
)
Comment thread
saba8814 marked this conversation as resolved.
Loading