@@ -34,6 +34,40 @@ log() {
3434 echo " [${timestamp} ] ${message} " | tee -a " $LOG_FILE "
3535}
3636
37+ # Function to perform portable sed in-place editing
38+ sed_inplace () {
39+ local options=" "
40+ local pattern=" "
41+ local file=" "
42+
43+ # Parse arguments to handle options like -E
44+ while [[ $# -gt 0 ]]; do
45+ case $1 in
46+ -E|-r)
47+ options=" $options $1 "
48+ shift
49+ ;;
50+ * )
51+ if [ -z " $pattern " ]; then
52+ pattern=" $1 "
53+ elif [ -z " $file " ]; then
54+ file=" $1 "
55+ fi
56+ shift
57+ ;;
58+ esac
59+ done
60+
61+ # Detect OS and use appropriate sed syntax
62+ if [[ " $OSTYPE " == " darwin" * ]]; then
63+ # macOS (BSD sed) requires empty string after -i
64+ sed -i ' ' $options " $pattern " " $file "
65+ else
66+ # Linux (GNU sed) doesn't require anything after -i
67+ sed -i $options " $pattern " " $file "
68+ fi
69+ }
70+
3771# Function to show usage
3872usage () {
3973 echo " Usage: $0 [--version VERSION --stage STAGE | --tag] [--date DATE] [--help]"
@@ -134,7 +168,7 @@ pre_update_checks() {
134168
135169 # Attempt to extract version from VERSION.json using sed
136170 log " Attempting to extract current version from $VERSION_FILE using sed..."
137- CURRENT_VERSION=$( sed -n ' s/^\s *"version"\s*:\s *"\([^"]*\)".*$/\1/p' " $VERSION_FILE " | head -n 1) # head -n 1 ensures only the first match is taken
171+ CURRENT_VERSION=$( sed -n ' s/^[[:space:]] *"version"[[:space:]]*:[[:space:]] *"\([^"]*\)".*$/\1/p' " $VERSION_FILE " | head -n 1) # head -n 1 ensures only the first match is taken
138172
139173 # Check if sed successfully extracted a version
140174 if [ -z " $CURRENT_VERSION " ]; then
@@ -151,7 +185,7 @@ pre_update_checks() {
151185
152186 # Attempt to extract stage from VERSION.json using sed
153187 log " Attempting to extract current stage from $VERSION_FILE using sed..."
154- CURRENT_STAGE=$( sed -n ' s/^\s *"stage"\s*:\s *"\([^"]*\)".*$/\1/p' " $VERSION_FILE " | head -n 1) # head -n 1 ensures only the first match is taken
188+ CURRENT_STAGE=$( sed -n ' s/^[[:space:]] *"stage"[[:space:]]*:[[:space:]] *"\([^"]*\)".*$/\1/p' " $VERSION_FILE " | head -n 1) # head -n 1 ensures only the first match is taken
155189
156190 # Check if sed successfully extracted a stage
157191 if [ -z " $CURRENT_STAGE " ]; then
@@ -168,7 +202,7 @@ pre_update_checks() {
168202
169203 # Attempt to extract current revision from package.json using sed
170204 log " Attempting to extract current revision from $PACKAGE_JSON using sed..."
171- CURRENT_REVISION=$( sed -n ' /"wazuh": {/,/}/ s/^\s *"revision"\s*:\s *"\([^"]*\)".*$/\1/p' " $PACKAGE_JSON " | head -n 1)
205+ CURRENT_REVISION=$( sed -n ' /"wazuh": {/,/}/ s/^[[:space:]] *"revision"[[:space:]]*:[[:space:]] *"\([^"]*\)".*$/\1/p' " $PACKAGE_JSON " | head -n 1)
172206
173207 if [ -z " $CURRENT_REVISION " ]; then
174208 log " ERROR: Failed to extract 'revision' from $PACKAGE_JSON using sed. Check file format or key presence."
@@ -226,7 +260,7 @@ compare_versions_and_set_revision() {
226260 # Versions are identical (Major, Minor, Patch are equal)
227261 log " New version ($VERSION ) is identical to current version ($CURRENT_VERSION )."
228262 log " Attempting to extract current revision from $PACKAGE_JSON using sed (Note: This is fragile)"
229- local current_revision_val=$( sed -n ' s/^\s *"revision"\s*:\s *"\([^"]*\)".*$/\1/p' " $PACKAGE_JSON " | head -n 1)
263+ local current_revision_val=$( sed -n ' s/^[[:space:]] *"revision"[[:space:]]*:[[:space:]] *"\([^"]*\)".*$/\1/p' " $PACKAGE_JSON " | head -n 1)
230264 # Check if sed successfully extracted a revision
231265 if [ -z " $current_revision_val " ]; then
232266 log " ERROR: Failed to extract 'revision' from $PACKAGE_JSON using sed. Check file format or key presence."
@@ -263,13 +297,13 @@ update_root_version_json() {
263297
264298 # Update version in VERSION.json
265299 if [ -n " $VERSION " ] && [ " $CURRENT_VERSION " != " $VERSION " ]; then
266- sed -i " s/^\s *\" version\" \s*:\s *\" [^\" ]*\" / \" version\" : \" $VERSION \" /" " $VERSION_FILE "
300+ sed_inplace " s/^[[:space:]] *\" version\" [[:space:]]*:[[:space:]] *\" [^\" ]*\" / \" version\" : \" $VERSION \" /" " $VERSION_FILE "
267301 modified=true
268302 fi
269303
270304 # Update stage in VERSION.json
271305 if [ -n " $STAGE " ] && [ " $CURRENT_STAGE " != " $STAGE " ]; then
272- sed -i " s/^\s *\" stage\" \s*:\s *\" [^\" ]*\" / \" stage\" : \" $STAGE \" /" " $VERSION_FILE "
306+ sed_inplace " s/^[[:space:]] *\" stage\" [[:space:]]*:[[:space:]] *\" [^\" ]*\" / \" stage\" : \" $STAGE \" /" " $VERSION_FILE "
273307 modified=true
274308 fi
275309
@@ -298,15 +332,15 @@ update_package_json() {
298332 # Note: This sed command assumes a specific formatting and might be fragile.
299333 # It looks for the block starting with a line containing "wazuh": { and ending with the next line containing only }
300334 # Within that block, it replaces the value on the line starting with "version":
301- sed -i " /\" wazuh\" : {/,/}/ s/^\(\s *\" version\" \s*:\s *\)\" [^\" ]*\" /\1\" $VERSION \" /" " $PACKAGE_JSON "
335+ sed_inplace " /\" wazuh\" : {/,/}/ s/^\([[:space:]] *\" version\" [[:space:]]*:[[:space:]] *\)\" [^\" ]*\" /\1\" $VERSION \" /" " $PACKAGE_JSON "
302336 modified=true
303337 fi
304338
305339 # Update revision in package.json
306340 if [[ " $CURRENT_REVISION " != " $REVISION " ]]; then
307341 log " Attempting to update revision to $REVISION within 'wazuh' object in $PACKAGE_JSON "
308342 # Similar sed command for the revision line within the same block
309- sed -i " /\" wazuh\" : {/,/}/ s/^\(\s *\" revision\" \s*:\s *\)\" [^\" ]*\" /\1\" $REVISION \" /" " $PACKAGE_JSON "
343+ sed_inplace " /\" wazuh\" : {/,/}/ s/^\([[:space:]] *\" revision\" [[:space:]]*:[[:space:]] *\)\" [^\" ]*\" /\1\" $REVISION \" /" " $PACKAGE_JSON "
310344 modified=true
311345 fi
312346
@@ -331,7 +365,7 @@ update_changelog() {
331365 # This is significantly less reliable than using jq.
332366 log " Attempting to extract .version from $VERSION_FILE using sed (Note: This is fragile)"
333367 # Extract OpenSearch Dashboards version from package.json (first occurrence of "version")
334- OPENSEARCH_VERSION=$( sed -n ' s/^\s *"version"\s*:\s *"\([^"]*\)".*$/\1/p' " $PACKAGE_JSON " | head -n 1)
368+ OPENSEARCH_VERSION=$( sed -n ' s/^[[:space:]] *"version"[[:space:]]*:[[:space:]] *"\([^"]*\)".*$/\1/p' " $PACKAGE_JSON " | head -n 1)
335369 if [ -z " $OPENSEARCH_VERSION " ] || [ " $OPENSEARCH_VERSION " == " null" ]; then
336370 log " ERROR: Could not extract pluginPlatform.version from $PACKAGE_JSON for changelog"
337371 exit 1
@@ -350,7 +384,7 @@ update_changelog() {
350384 if [ -n " $STAGE " ]; then
351385 log " Changelog entry for this version and OpenSearch Dashboards version exists. Updating revision only."
352386 # Use sed to update only the revision number in the header
353- sed -i -E " s|(${changelog_header_regex} )|${changelog_header}${REVISION} |" " $changelog_file " &&
387+ sed_inplace -E " s|(${changelog_header_regex} )|${changelog_header}${REVISION} |" " $changelog_file " &&
354388 log " CHANGELOG.md revision updated successfully." || {
355389 log " ERROR: Failed to update revision in $changelog_file "
356390 exit 1
@@ -395,7 +429,7 @@ update_build_workflow() {
395429 if grep -qE ' \.yml@[^"[:space:]]+' " $WAZUH_DASHBOARD_PLUGINS_WORKFLOW_FILE " ; then
396430 log " Pattern found in $( basename $WAZUH_DASHBOARD_PLUGINS_WORKFLOW_FILE ) . Attempting update..."
397431 # If the pattern exists, perform the substitution
398- sed -i -E " s/(\.yml@)[^\" [:space:]]+/\1${replacement} /g" " $WAZUH_DASHBOARD_PLUGINS_WORKFLOW_FILE "
432+ sed_inplace -E " s/(\.yml@)[^\" [:space:]]+/\1${replacement} /g" " $WAZUH_DASHBOARD_PLUGINS_WORKFLOW_FILE "
399433 modified=true
400434 else
401435 log " Pattern not found in $( basename $WAZUH_DASHBOARD_PLUGINS_WORKFLOW_FILE ) . Skipping update."
@@ -418,14 +452,14 @@ update_base_package_dockerfile() {
418452 if grep -qE " $branch_pattern_regex " " $DOCKERFILE_FOR_BASE_PACKAGES " ; then
419453 log " Pattern '$branch_pattern_regex ' found in $( basename $DOCKERFILE_FOR_BASE_PACKAGES ) . Attempting update..."
420454 # Perform the substitution
421- sed -i -E " s/${branch_pattern_regex} /\1${VERSION} /g" " $DOCKERFILE_FOR_BASE_PACKAGES "
455+ sed_inplace -E " s/${branch_pattern_regex} /\1${VERSION} /g" " $DOCKERFILE_FOR_BASE_PACKAGES "
422456 modified=true
423457 else
424458 log " Pattern '$branch_pattern_regex ' not found in $( basename $DOCKERFILE_FOR_BASE_PACKAGES ) . Skipping update for this pattern."
425459 fi
426460
427461 # Update all occurrences of wazuh-packages-to-base:x.y.z with wazuh-packages-to-base:$VERSION
428- sed -i -E " s/(wazuh-packages-to-base:)$VERSION_PATTERN /\1${VERSION} /g" " $DOCKERFILE_FOR_BASE_PACKAGES " && modified=true
462+ sed_inplace -E " s/(wazuh-packages-to-base:)$VERSION_PATTERN /\1${VERSION} /g" " $DOCKERFILE_FOR_BASE_PACKAGES " && modified=true
429463
430464 if [[ $modified == true ]]; then
431465 log " Successfully updated $( basename $DOCKERFILE_FOR_BASE_PACKAGES ) "
@@ -447,7 +481,7 @@ update_readme_for_base_packages() {
447481 if grep -qE " $app_pattern_regex " " $README_FOR_BASE_PACKAGES " ; then
448482 log " Pattern '$app_pattern_regex ' found in $( basename $README_FOR_BASE_PACKAGES ) . Attempting update..."
449483 # If the pattern exists, perform the substitution and set modified to true
450- sed -i -E " s/${app_pattern_regex} /\1${VERSION} /g" " $README_FOR_BASE_PACKAGES "
484+ sed_inplace -E " s/${app_pattern_regex} /\1${VERSION} /g" " $README_FOR_BASE_PACKAGES "
451485 modified=true
452486 else
453487 log " Pattern '$app_pattern_regex ' not found in $( basename $README_FOR_BASE_PACKAGES ) . Skipping update for this pattern."
@@ -461,7 +495,7 @@ update_readme_for_base_packages() {
461495 if grep -qE " $base_pattern_regex " " $README_FOR_BASE_PACKAGES " ; then
462496 log " Pattern '$base_pattern_regex ' found in $( basename $README_FOR_BASE_PACKAGES ) . Attempting update..."
463497 # If the pattern exists, perform the substitution and set modified to true
464- sed -i -E " s/${base_pattern_regex} /\1${VERSION} /g" " $README_FOR_BASE_PACKAGES "
498+ sed_inplace -E " s/${base_pattern_regex} /\1${VERSION} /g" " $README_FOR_BASE_PACKAGES "
465499 modified=true
466500 else
467501 log " Pattern '$base_pattern_regex ' not found in $( basename $README_FOR_BASE_PACKAGES ) . Skipping update for this pattern."
@@ -475,7 +509,7 @@ update_readme_for_base_packages() {
475509 if grep -qE " $security_pattern_regex " " $README_FOR_BASE_PACKAGES " ; then
476510 log " Pattern '$security_pattern_regex ' found in $( basename $README_FOR_BASE_PACKAGES ) . Attempting update..."
477511 # If the pattern exists, perform the substitution and set modified to true
478- sed -i -E " s/${security_pattern_regex} /\1${VERSION} /g" " $README_FOR_BASE_PACKAGES "
512+ sed_inplace -E " s/${security_pattern_regex} /\1${VERSION} /g" " $README_FOR_BASE_PACKAGES "
479513 modified=true
480514 else
481515 log " Pattern '$security_pattern_regex ' not found in $( basename $README_FOR_BASE_PACKAGES ) . Skipping update for this pattern."
@@ -489,7 +523,7 @@ update_readme_for_base_packages() {
489523 if grep -qE " $readme_example_pattern_regex " " $README_FOR_BASE_PACKAGES " ; then
490524 log " Pattern '$readme_example_pattern_regex ' found in $( basename $README_FOR_BASE_PACKAGES ) . Attempting update..."
491525 # If the pattern exists, perform the substitution and set modified to true
492- sed -i -E " s/${readme_example_pattern_regex} /\1${VERSION} /g" " $README_FOR_BASE_PACKAGES "
526+ sed_inplace -E " s/${readme_example_pattern_regex} /\1${VERSION} /g" " $README_FOR_BASE_PACKAGES "
493527 modified=true
494528 else
495529 log " Pattern '$readme_example_pattern_regex ' not found in $( basename $README_FOR_BASE_PACKAGES ) . Skipping update for this pattern."
@@ -514,7 +548,7 @@ update_rendering_service_test_snap() {
514548 if grep -qE " $pattern_regex " " $rendering_service_test_snap " ; then
515549 log " Pattern '$pattern_regex ' found in $( basename $rendering_service_test_snap ) . Attempting update..."
516550 # If the pattern exists, perform the substitution
517- sed -i -E " s/${pattern_regex} /\1${VERSION} /g" " $rendering_service_test_snap "
551+ sed_inplace -E " s/${pattern_regex} /\1${VERSION} /g" " $rendering_service_test_snap "
518552 log " Successfully updated rendering service test snapshot."
519553 else
520554 log " Pattern '$pattern_regex ' not found in $( basename $rendering_service_test_snap ) . Skipping update for this pattern."
@@ -528,21 +562,37 @@ update_rendering_service_test_snap() {
528562# Function to convert date from yyyy-mm-dd to RPM format (e.g., "Thu Sep 04 2025")
529563convert_date_to_rpm_format () {
530564 local input_date=" $1 "
531- # Use date command to convert and format with English locale
532- LC_ALL=C date -d " $input_date " " +%a %b %d %Y" 2> /dev/null || {
533- log " ERROR: Invalid date format: $input_date "
534- exit 1
535- }
565+ # Use date command to convert and format with English locale (GNU date on Linux, BSD date on macOS)
566+ if [[ " $OSTYPE " == " darwin" * ]]; then
567+ LC_ALL=C date -j -f " %Y-%m-%d" " $input_date " " +%a %b %d %Y" 2> /dev/null && return 0
568+ else
569+ LC_ALL=C date -d " $input_date " " +%a %b %d %Y" 2> /dev/null && return 0
570+ fi
571+ log " ERROR: Invalid date format: $input_date "
572+ return 1
573+ }
574+
575+ # Function to convert an RPM changelog date (e.g. "Thu Sep 10 2026") to epoch seconds, for ordering comparisons
576+ rpm_date_to_epoch () {
577+ local rpm_date=" $1 "
578+ if [[ " $OSTYPE " == " darwin" * ]]; then
579+ LC_ALL=C date -j -f " %a %b %d %Y" " $rpm_date " " +%s" 2> /dev/null
580+ else
581+ LC_ALL=C date -d " $rpm_date " " +%s" 2> /dev/null
582+ fi
536583}
537584
538585# Function to convert date from yyyy-mm-dd to Debian RFC 2822 format (e.g., "Thu, 04 Sep 2025 12:00:00 +0000")
539586convert_date_to_deb_format () {
540587 local input_date=" $1 "
541- # Use date command to convert and format with English locale
542- LC_ALL=C date -d " $input_date " " +%a, %d %b %Y 12:00:00 +0000" 2> /dev/null || {
543- log " ERROR: Invalid date format: $input_date "
544- exit 1
545- }
588+ # Use date command to convert and format with English locale (GNU date on Linux, BSD date on macOS)
589+ if [[ " $OSTYPE " == " darwin" * ]]; then
590+ LC_ALL=C date -j -f " %Y-%m-%d" " $input_date " " +%a, %d %b %Y 12:00:00 +0000" 2> /dev/null && return 0
591+ else
592+ LC_ALL=C date -d " $input_date " " +%a, %d %b %Y 12:00:00 +0000" 2> /dev/null && return 0
593+ fi
594+ log " ERROR: Invalid date format: $input_date "
595+ return 1
546596}
547597
548598# Function to update RPM changelog
@@ -560,24 +610,50 @@ update_rpm_changelog() {
560610
561611 log " Updating RPM changelog..."
562612
563- local rpm_date=$( convert_date_to_rpm_format " $DATE " )
613+ local rpm_date
614+ rpm_date=$( convert_date_to_rpm_format " $DATE " ) || exit 1
564615 local changelog_entry=" * $rpm_date support <info@wazuh.com> - $VERSION "
565616 local more_info_entry=" - More info: https://documentation.wazuh.com/current/release-notes/release-$( echo $VERSION | tr ' .' ' -' ) .html"
566617
618+ local new_epoch
619+ new_epoch=$( rpm_date_to_epoch " $rpm_date " ) || exit 1
620+
567621 # Check if entry already exists
568- if grep -q " * .* support <info@wazuh.com> - $VERSION " " $RPM_CHANGELOG " ; then
569- log " RPM changelog entry for version $VERSION already exists. Updating date..."
570- # Update existing entry date
571- sed -i " s/^\* .* support <info@wazuh.com> - $VERSION $/$changelog_entry /" " $RPM_CHANGELOG "
572- log " Successfully updated RPM changelog date for version $VERSION "
622+ if grep -q " ^\* .* support <info@wazuh.com> - $VERSION \$ " " $RPM_CHANGELOG " ; then
623+ log " RPM changelog entry for version $VERSION already exists. Repositioning it with the updated date..."
624+ # Remove the existing entry (its changelog line and following "More info" line);
625+ # it gets reinserted below at the position matching its (possibly new) date, so
626+ # entries stay in descending order regardless of where the old entry used to sit.
627+ sed_inplace " /^\* .* support <info@wazuh.com> - $VERSION \$ /,+1d" " $RPM_CHANGELOG "
573628 else
574629 log " Adding new RPM changelog entry for version $VERSION ..."
575- # Find the %changelog line and add new entry after it
576- sed -i " /^%changelog$/a\\
630+ fi
631+
632+ # Entries must stay in descending date order (rpmbuild enforces it), so insert the
633+ # entry right before the first existing entry whose date is not newer than it.
634+ local insert_before_line=" "
635+ local line_num=0
636+ while IFS= read -r line; do
637+ line_num=$(( line_num + 1 ))
638+ if [[ " $line " =~ ^\*\ (.* )\ support ]]; then
639+ local existing_epoch
640+ existing_epoch=$( rpm_date_to_epoch " ${BASH_REMATCH[1]} " ) || continue
641+ if [ " $existing_epoch " -le " $new_epoch " ]; then
642+ insert_before_line=$line_num
643+ break
644+ fi
645+ fi
646+ done < " $RPM_CHANGELOG "
647+
648+ if [ -n " $insert_before_line " ]; then
649+ sed_inplace " ${insert_before_line} i\\
577650$changelog_entry \\
578651$more_info_entry " " $RPM_CHANGELOG "
579- log " Successfully added new RPM changelog entry for version $VERSION "
652+ else
653+ # New entry is older than every existing one (or there are no entries yet): goes last
654+ printf ' %s\n%s\n' " $changelog_entry " " $more_info_entry " >> " $RPM_CHANGELOG "
580655 fi
656+ log " Successfully updated RPM changelog entry for version $VERSION "
581657}
582658
583659# Function to update Debian changelog
@@ -595,7 +671,8 @@ update_deb_changelog() {
595671
596672 log " Updating Debian changelog..."
597673
598- local deb_date=$( convert_date_to_deb_format " $DATE " )
674+ local deb_date
675+ deb_date=$( convert_date_to_deb_format " $DATE " ) || exit 1
599676 local package_version=" $VERSION -RELEASE"
600677 local changelog_header=" wazuh-dashboard ($package_version ) stable; urgency=low"
601678 local more_info_entry=" * More info: https://documentation.wazuh.com/current/release-notes/release-$( echo $VERSION | tr ' .' ' -' ) .html"
@@ -609,7 +686,7 @@ update_deb_changelog() {
609686 log " Debian changelog entry for version $VERSION already exists. Updating date..."
610687 # Update existing entry date
611688 # Find the line with the version and then find the next maintainer line to update
612- sed -i " /wazuh-dashboard ($escaped_package_version )/,/^ *-- Wazuh, Inc/ s|^ *-- Wazuh, Inc <info@wazuh.com> .*|$maintainer_line |" " $DEB_CHANGELOG "
689+ sed_inplace " /wazuh-dashboard ($escaped_package_version )/,/^ *-- Wazuh, Inc/ s|^ *-- Wazuh, Inc <info@wazuh.com> .*|$maintainer_line |" " $DEB_CHANGELOG "
613690 log " Successfully updated Debian changelog date for version $VERSION "
614691 else
615692 log " Adding new Debian changelog entry for version $VERSION ..."
0 commit comments