diff --git a/mysql/CHANGELOG b/mysql/CHANGELOG
index 82fd19c5b..46f6c18d5 100644
--- a/mysql/CHANGELOG
+++ b/mysql/CHANGELOG
@@ -1,5 +1,6 @@
---- Changes since 2.641 ----
Added defaults for alpine linux and fix package installs
+Added a Binary Logging page for enabling binary logs, setting their format, maximum file size and retention period, rotating logs on demand, and deleting old log files.
---- Changes since 1.140 ----
The not null flag and a default value can be specified for fields in new tables.
The form for creating an initial table in a new database is now the same as the one for adding a table to an existing database.
diff --git a/mysql/edit_binlogs.cgi b/mysql/edit_binlogs.cgi
new file mode 100755
index 000000000..219fd1957
--- /dev/null
+++ b/mysql/edit_binlogs.cgi
@@ -0,0 +1,169 @@
+#!/usr/local/bin/perl
+# Show binary logging status, configuration options and log cleanup
+
+require './mysql-lib.pl';
+$access{'perms'} == 1 || &error($text{'binlogs_ecannot'});
+&ReadParse();
+&ui_print_header(undef, $text{'binlogs_title'}, "", "binlogs");
+
+# Make sure config exists
+$conf = &get_mysql_config();
+if (!$conf) {
+ print &text('cnf_efile', "$config{'my_cnf'}",
+ "../config.cgi?$module_name"),"
\n";
+ &ui_print_footer("", $text{'index_return'});
+ exit;
+ }
+# Prefer the main server section over generic MariaDB sections that can
+# belong to a plugin-specific include file
+($mysqld) = grep { $_->{'name'} eq 'mysqld' } @$conf;
+($mysqld) = grep { $_->{'name'} eq 'mariadbd' } @$conf if (!$mysqld);
+($mysqld) = grep { $_->{'name'} eq 'mariadb' } @$conf if (!$mysqld);
+$mysqld || &error($text{'cnf_emysqld'});
+$mems = $mysqld->{'members'};
+$local = &is_mysql_local();
+
+# Get the status reported by the running server
+($curfile, $curpos) = &get_binary_log_status();
+@logs = &list_binary_logs();
+$liveformat = eval {
+ local $main::error_must_die = 1;
+ my $d = &execute_sql($master_db,
+ "show variables like 'binlog_format'");
+ @{$d->{'data'}} ? $d->{'data'}->[0]->[1] : undef;
+ };
+
+# Show tabs for configuration and log cleanup
+@tabs = ( [ 'options', $text{'binlogs_tab_options'} ],
+ $curfile ? ( [ 'purge', $text{'binlogs_tab_purge'} ] ) : ( ) );
+%valid = map { $_->[0] => 1 } @tabs;
+$mode = $in{'mode'} && $valid{$in{'mode'}} ? $in{'mode'} : 'options';
+print &ui_tabs_start(\@tabs, "mode", $mode, 1);
+
+# Configuration tab
+print &ui_tabs_start_tab("mode", "options");
+print &ui_div($text{'binlogs_desc_options'});
+print &ui_form_start("save_binlogs.cgi", "post");
+print &ui_table_start($text{'binlogs_header'}, "width=100%", 2);
+
+# Current state, as one read-only row
+if ($curfile) {
+ $total = 0;
+ foreach $l (@logs) {
+ $total += $l->{'size'};
+ }
+ print &ui_table_row($text{'binlogs_active'},
+ &text('binlogs_activeyes', "".&html_escape($curfile)."",
+ $curpos, scalar(@logs), &nice_size($total)).
+ ($liveformat ? " ".&text('binlogs_activefmt',
+ uc($liveformat)) : ""));
+ }
+else {
+ print &ui_table_row($text{'binlogs_active'}, $text{'no'});
+ }
+
+if ($local) {
+ # Enabled state considers both positive and negative directives, the
+ # live server status, and finally whether this server version logs
+ # by default when nothing is configured (as MySQL 8+ does)
+ $lbdir = &find("log_bin", $mems) || &find("log-bin", $mems);
+ $skipdir = &find("skip-log-bin", $mems) ||
+ &find("skip_log_bin", $mems) ||
+ &find("disable-log-bin", $mems) ||
+ &find("disable_log_bin", $mems);
+ $lb = $lbdir ? $lbdir->{'value'} : undef;
+ $enabled = $skipdir ? 0 : $lbdir ? 1 : $curfile ? 1 :
+ &get_binlog_default_on();
+ print &ui_table_row(&hlink($text{'binlogs_enabled'},
+ "binlogs_enabled"),
+ &ui_yesno_radio("enabled", $enabled));
+
+ print &ui_table_row(&hlink($text{'binlogs_base'}, "binlogs_base"),
+ &ui_opt_textbox("base", $lb, 30, $text{'binlogs_base_def'}));
+
+ $sid = &find_value("server_id", $mems);
+ $sid = &find_value("server-id", $mems) if (!defined($sid));
+ print &ui_table_row(&hlink($text{'binlogs_serverid'},
+ "binlogs_serverid"),
+ &ui_opt_textbox("serverid", $sid, 10, $text{'default'}));
+
+ # Log format, which must be ROW for point-in-time recovery
+ $fmt = &find_value("binlog_format", $mems);
+ $fmt = &find_value("binlog-format", $mems) if (!defined($fmt));
+ print &ui_table_row(&hlink($text{'binlogs_format'},
+ "binlogs_format"),
+ &ui_select("format", uc($fmt // ''),
+ [ [ '', $text{'default'} ],
+ [ 'ROW', $text{'binlogs_format_row'} ],
+ [ 'MIXED', $text{'binlogs_format_mixed'} ],
+ [ 'STATEMENT',
+ $text{'binlogs_format_statement'} ] ]));
+
+ # Maximum size of one log file before rotation
+ $maxsize = &find_value("max_binlog_size", $mems);
+ $maxsize = &find_value("max-binlog-size", $mems)
+ if (!defined($maxsize));
+ print &ui_table_row(&hlink($text{'binlogs_maxsize'},
+ "binlogs_maxsize"),
+ &ui_radio("maxsize_def", defined($maxsize) ? 0 : 1,
+ [ [ 1, $text{'default'} ], [ 0, " " ] ])."\n".
+ &mysql_size_input("maxsize", $maxsize));
+
+ # Retention period, stored in a variant-specific variable
+ ($ver, $variant) = &get_mysql_variant_cached();
+ $secs = &find_value("binlog_expire_logs_seconds", $mems);
+ $days = &find_value("expire_logs_days", $mems);
+ $expire = defined($secs) ? &format_binlog_expire_days($secs) :
+ defined($days) ? $days : undef;
+ print &ui_table_row(&hlink($text{'binlogs_expire'},
+ "binlogs_expire"),
+ &ui_opt_textbox("expire", $expire, 6,
+ $text{'binlogs_expire_def'}).
+ " ".$text{'binlogs_days'});
+
+ print &ui_table_end();
+ print &ui_form_end([ [ "save", $text{'save'} ],
+ &is_mysql_running() > 0 ?
+ ( [ "restart",
+ $text{'binlogs_restart'} ] ) : ( ) ]);
+ }
+else {
+ # Config file editing only works for a local database server
+ print &ui_table_end();
+ print &ui_form_end();
+ print &ui_alert($text{'binlogs_eremote'}, 'info');
+ }
+print &ui_tabs_end_tab("mode", "options");
+
+# Log cleanup tab
+if ($curfile) {
+ print &ui_tabs_start_tab("mode", "purge");
+ print &ui_div($text{'binlogs_desc_purge'});
+ print &ui_form_start("purge_binlogs.cgi", "post");
+ print &ui_table_start($text{'binlogs_purgeheader'},
+ "width=100%", 2);
+ print &ui_table_row(&hlink($text{'binlogs_purge'},
+ "binlogs_purge"),
+ &ui_radio("mode", 0,
+ [ [ 0, &text('binlogs_purgedays',
+ &ui_textbox("days", 7, 6))." " ],
+ [ 1, $text{'binlogs_purgeall'} ] ]));
+ print &ui_table_end();
+ print &ui_form_end([ [ "purge", $text{'binlogs_purgeok'} ] ]);
+ print &ui_tabs_end_tab("mode", "purge");
+ }
+
+print &ui_tabs_end();
+
+# Button to close the current log and start a new one, which gives backups
+# and log deletion a clean boundary to work with
+if ($curfile) {
+ print &ui_hr();
+ print &ui_buttons_start();
+ print &ui_buttons_row("flush_binlogs.cgi", $text{'binlogs_rotateok'},
+ &text('binlogs_rotatedesc',
+ "".&html_escape($curfile).""));
+ print &ui_buttons_end();
+ }
+
+&ui_print_footer("", $text{'index_return'});
diff --git a/mysql/edit_cnf.cgi b/mysql/edit_cnf.cgi
index 9a6055dea..eaa35b820 100755
--- a/mysql/edit_cnf.cgi
+++ b/mysql/edit_cnf.cgi
@@ -13,9 +13,11 @@ if (!$conf) {
&ui_print_footer("", $text{'index_return'});
exit;
}
-($mysqld) = grep { $_->{'name'} eq 'mysqld' ||
- $_->{'name'} eq 'mariadbd' ||
- $_->{'name'} eq 'mariadb' } @$conf;
+# Prefer the main server section over generic MariaDB sections that can
+# belong to a plugin-specific include file
+($mysqld) = grep { $_->{'name'} eq 'mysqld' } @$conf;
+($mysqld) = grep { $_->{'name'} eq 'mariadbd' } @$conf if (!$mysqld);
+($mysqld) = grep { $_->{'name'} eq 'mariadb' } @$conf if (!$mysqld);
$mysqld || &error($text{'cnf_emysqld'});
$mems = $mysqld->{'members'};
@@ -110,4 +112,3 @@ print &ui_form_end([ [ "save", $text{'save'} ],
[ "restart", $text{'cnf_restart'} ] ]);
&ui_print_footer("", $text{'index_return'});
-
diff --git a/mysql/flush_binlogs.cgi b/mysql/flush_binlogs.cgi
new file mode 100755
index 000000000..f859da3df
--- /dev/null
+++ b/mysql/flush_binlogs.cgi
@@ -0,0 +1,13 @@
+#!/usr/local/bin/perl
+# Close the current binary log and start writing a new one
+
+require './mysql-lib.pl';
+$access{'perms'} == 1 || &error($text{'binlogs_ecannot'});
+&error_setup($text{'binlogs_rotateerr'});
+&ReadParse();
+
+my ($curfile) = &get_binary_log_status();
+$curfile || &error($text{'binlogs_epurgeoff'});
+&rotate_binary_logs();
+&webmin_log("flushbinlogs");
+&redirect("edit_binlogs.cgi");
diff --git a/mysql/help/binlogs.html b/mysql/help/binlogs.html
new file mode 100644
index 000000000..265ae6044
--- /dev/null
+++ b/mysql/help/binlogs.html
@@ -0,0 +1,18 @@
+Binary Logging
+
+This page can be used to enable or disable binary logging on the MySQL or
+MariaDB database server, and to manage existing binary log files. Binary logs
+record all changes made to databases, and are needed for replication and for
+point-in-time recovery of backups.
+
+When binary logging is enabled, log files are written under the configured
+base name and grow as changes are made. To stop them from filling up the disk,
+a retention period can be set after which old logs are automatically deleted,
+or old logs can be deleted immediately using the form at the bottom of the
+page.
+
+For any changes on this page to take effect, the MySQL server must be
+re-started, either with the Save and Restart MySQL button or using the
+buttons on the module's main page.