Skip to content
Merged
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
4 changes: 2 additions & 2 deletions rust/WEB-SERVER.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ Some more examples:
- IPv4, only specific interface: `--address 192.168.1.2:5678` (use the IP
address of that interface)

The server can optionally listen on a secondary address, use the `--address2`
option for that.
The server can listen on several addresses, you can use the `--address` option
multiple times.

## Trying the server

Expand Down
50 changes: 25 additions & 25 deletions rust/agama-server/src/agama-web-server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,9 @@ struct Cli {
struct ServeArgs {
// Address/port to listen on. ":::80" listens for both IPv6 and IPv4
// connections unless manually disabled in /proc/sys/net/ipv6/bindv6only.
/// Primary port to listen on
/// Address:port to listen to, with comma-separated fallback address:port; can be repeated
#[arg(long, default_value = ":::80")]
address: String,

/// Optional secondary address to listen on
#[arg(long, default_value = None)]
address2: Option<String>,
address: Vec<String>,

#[arg(long, default_value = "/etc/agama.d/ssl/key.pem")]
key: Option<PathBuf>,
Expand Down Expand Up @@ -273,19 +269,28 @@ async fn handle_http_stream(
}
}

async fn find_listener(addresses: String) -> Option<tokio::net::TcpListener> {
let addresses = addresses.split(',').collect::<Vec<_>>();
for addr in addresses {
tracing::info!("Starting Agama web server at {}", addr);
// see https://github.com/tokio-rs/axum/blob/main/examples/low-level-openssl/src/main.rs
// how to use axum with openSSL
match tokio::net::TcpListener::bind(&addr).await {
Ok(listener) => {
return Some(listener);
}
Err(error) => {
tracing::warn!("Error: could not listen on {}: {}", &addr, error);
}
}
}
None
}

/// Starts the web server
async fn start_server(address: String, service: Router, ssl_acceptor: SslAcceptor) {
tracing::info!("Starting Agama web server at {}", address);

// see https://github.com/tokio-rs/axum/blob/main/examples/low-level-openssl/src/main.rs
// how to use axum with openSSL
let listener = tokio::net::TcpListener::bind(&address)
.await
.unwrap_or_else(|error| {
let msg = format!("Error: could not listen on {}: {}", &address, error);
tracing::error!(msg);
panic!("{}", msg)
});
let opt_listener = find_listener(address).await;
let listener = opt_listener.expect("None of the alternative addresses worked");

pin_mut!(listener);

Expand All @@ -300,7 +305,7 @@ async fn start_server(address: String, service: Router, ssl_acceptor: SslAccepto
let (tcp_stream, addr) = listener
.accept()
.await
.expect("Failed to open port for listening");
.expect("Failed to accept connection");

tokio::spawn(async move {
if is_ssl_stream(&tcp_stream).await {
Expand Down Expand Up @@ -341,13 +346,8 @@ async fn serve_command(args: ServeArgs) -> anyhow::Result<()> {
return Err(anyhow::anyhow!("SSL initialization failed"));
};

let mut addresses = vec![args.address];

if let Some(a) = args.address2 {
addresses.push(a)
}

let servers: Vec<_> = addresses
let servers: Vec<_> = args
.address
.iter()
.map(|a| {
tokio::spawn(start_server(
Expand Down
1 change: 1 addition & 0 deletions rust/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ install -D -t "${DESTDIR}${bindir}" "${SRCDIR}/target/${RUST_TARGET}/agama"
install -D -t "${DESTDIR}${bindir}" "${SRCDIR}/target/${RUST_TARGET}/agama-autoinstall"
install -D -t "${DESTDIR}${bindir}" "${SRCDIR}/target/${RUST_TARGET}/agama-proxy-setup"
install -D -t "${DESTDIR}${bindir}" "${SRCDIR}/target/${RUST_TARGET}/agama-web-server"
install -D -t "${DESTDIR}${bindir}" "${SRCDIR}/share/agama-web-server.sh"

install6 -D -p "${SRCDIR}"/share/agama.pam "${DESTDIR}${pamvendordir}"/agama

Expand Down
10 changes: 10 additions & 0 deletions rust/package/agama.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
-------------------------------------------------------------------
Thu Mar 12 13:05:51 UTC 2026 - Ladislav Slezák <lslezak@suse.com>

- Added support for the "inst.listen_on" boot parameter
- The "inst.listen_on=localhost" option disables remote access
- It is possible to specify IP address (both IPv4 and IPv6)
or network interface, accepts comma separated list
- agama-web-server --address2 option removed, --address can now be repeated
(jsc#AGM-153)

-------------------------------------------------------------------
Thu Mar 12 10:03:39 UTC 2026 - Imobach Gonzalez Sosa <igonzalezsosa@suse.com>

Expand Down
1 change: 1 addition & 0 deletions rust/package/agama.spec
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ echo $PATH
%doc README.md
%license LICENSE
%{_bindir}/agama-web-server
%{_bindir}/agama-web-server.sh
%{_bindir}/agama-proxy-setup
%{_pam_vendordir}/agama
%{_unitdir}/agama-web-server.service
Expand Down
2 changes: 1 addition & 1 deletion rust/share/agama-web-server.service
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ BindsTo=agama.service
EnvironmentFile=-/run/agama/environment.conf
Environment="AGAMA_LOG=debug,zbus=info"
Type=notify
ExecStart=/usr/bin/agama-web-server serve --address :::80 --address2 :::443
ExecStart=/usr/bin/agama-web-server.sh
PIDFile=/run/agama/web.pid
User=root
TimeoutStopSec=5
Expand Down
97 changes: 97 additions & 0 deletions rust/share/agama-web-server.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/usr/bin/bash
#
# Copyright (c) [2026] SUSE LLC
#
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 2 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, contact SUSE LLC.
#
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

# This script is a wrapper for the Agama web server, it evaluates to which
# addresses the server should listen to.

if [[ "$1" == "-h" || "$1" == "--help" ]]; then
echo "Usage: $0"
echo
echo " This is a wrapper script for the Agama web server (agama-web-server)."
echo
echo " It configures the listening addresses for the web server based on"
echo " the \"inst.listen_on\" boot option."
exit 0
fi

# the default options: listen on all interfaces for both HTTP and HTTPS ports,
# the IPv4 addresses are fallbacks when IPv6 is disabled with the
# "ipv6.disable=1" kernel boot option
DEFAULT_OPTIONS=(--address ":::80,0.0.0.0:80" --address ":::443,0.0.0.0:443")
# options for localhost access only
LOCAL_OPTIONS=(--address "::1:80,127.0.0.1:80" --address "::1:443,127.0.0.1:443")

# check if the "inst.listen_on=" boot option was used
if grep -q "\binst.listen_on=.\+" /run/agama/cmdline.d/agama.conf; then
LISTEN_ON=$(grep "\binst.listen_on=.\+" /run/agama/cmdline.d/agama.conf | sed 's/.*\binst.listen_on=\([^[:space:]]\+\)/\1/')

if [ "$LISTEN_ON" = "localhost" ]; then
OPTIONS=("${LOCAL_OPTIONS[@]}")
elif [ "$LISTEN_ON" = "all" ]; then
OPTIONS=("${DEFAULT_OPTIONS[@]}")
else
# always run on the localhost
OPTIONS=("${LOCAL_OPTIONS[@]}")

# the string can contain multiple addresses separated by comma,
# replace commas with spaces and iterate over items
ADDRESSES=${LISTEN_ON//,/ }
for ADDRESS in $ADDRESSES; do
# check if the value is an IP address (IPv6, IPv6 link local or IPv4)
if echo "$ADDRESS" | grep -qE '^[0-9a-fA-F:]+$|^[fF][eE]80|^([0-9]{1,3}\.){3}[0-9]{1,3}$'; then
echo "<5>Listening on IP address ${ADDRESS}"
OPTIONS+=(--address "${ADDRESS}:80" --address "${ADDRESS}:443")
else
# otherwise assume it is as an interface name
if ip addr show dev "${ADDRESS}" >/dev/null 2>&1; then
# find the IP address for the specified interface
IP_ADDRS=$(ip -o addr show dev "${ADDRESS}" | awk '{print $4}' | cut -d/ -f1)
if [ -n "${IP_ADDRS}" ]; then
for IP in $IP_ADDRS; do
# append the %device for link local IPv6 addresses
if [[ "$IP" == fe80* ]]; then
IP="${IP}%${ADDRESS}"
fi
echo "<5>Listening on interface ${ADDRESS} with IP address ${IP}"
OPTIONS+=(--address "${IP}:80" --address "${IP}:443")
done
else
echo "<3>IP address for interface ${ADDRESS} not found"
fi
else
echo "<3>Network Interface ${ADDRESS} not found"
fi
fi
done
fi
else
OPTIONS=("${DEFAULT_OPTIONS[@]}")
fi

if [ "${OPTIONS[*]}" = "${DEFAULT_OPTIONS[*]}" ]; then
echo "<5>Listening on all network interfaces"
elif [ "${OPTIONS[*]}" = "${LOCAL_OPTIONS[*]}" ]; then
echo "<5>Disabling remote access to the Agama web server"
fi

echo "<5>Starting Agama web server with options: ${OPTIONS[*]}"
exec /usr/bin/agama-web-server serve "${OPTIONS[@]}"
3 changes: 1 addition & 2 deletions rust/zypp-agama/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -597,8 +597,7 @@ impl Zypp {
// save the solver testcase if the solver run failed or if saving is forced via boot
// parameter, skip when "ZYPP_FULLLOG=1", in that case libzypp creates the solver
// testcase automatically in the /var/log/YaST2/autoTestcase/ directory
if (!r_res || save_testcase) && std::env::var("ZYPP_FULLLOG").unwrap_or_default() != "1"
{
if (!r_res || save_testcase) && !std::env::var("ZYPP_FULLLOG").is_ok_and(|v| v == "1") {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this from a different PR?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, related to #3259, I was just lazy to open a separate PR for this trivial one-liner...

self.create_solver_testcase();
} else {
// delete the solver testcase directory, it contains the previous error which is
Expand Down
2 changes: 1 addition & 1 deletion rust/zypp-agama/zypp-agama-sys/c-layer/include/lib.h
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ bool is_package_selected(struct Zypp *zypp, const char *tag,
bool run_solver(struct Zypp *zypp, bool only_required,
struct Status *status) noexcept;

/// Create a solver testcase, dumps all all solver data (repositories, loaded
/// Create a solver testcase, dumps all solver data (repositories, loaded
/// packages...) to disk
/// @param zypp see \ref init_target
/// @param dir directory path where the solver testcase is saved
Expand Down
2 changes: 1 addition & 1 deletion rust/zypp-agama/zypp-agama-sys/src/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,7 @@ unsafe extern "C" {
) -> bool;
#[doc = " Runs solver\n @param zypp see \\ref init_target\n @param only_required if true, only required packages are installed (ignoring\n recommended packages)\n @param[out] status (will overwrite existing contents)\n @return true if solver pass and false if it found some dependency issues"]
pub fn run_solver(zypp: *mut Zypp, only_required: bool, status: *mut Status) -> bool;
#[doc = " Create a solver testcase, dumps all all solver data (repositories, loaded\n packages...) to disk\n @param zypp see \\ref init_target\n @param dir directory path where the solver testcase is saved\n @return true if the solver testcase was successfully created"]
#[doc = " Create a solver testcase, dumps all solver data (repositories, loaded\n packages...) to disk\n @param zypp see \\ref init_target\n @param dir directory path where the solver testcase is saved\n @return true if the solver testcase was successfully created"]
pub fn create_solver_testcase(zypp: *mut Zypp, dir: *const ::std::os::raw::c_char) -> bool;
#[doc = " the last call that will free all pointers to zypp holded by agama"]
pub fn free_zypp(zypp: *mut Zypp);
Expand Down
2 changes: 1 addition & 1 deletion web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Example of running from different machine:
# backend machine
# using ip of machine instead of localhost is important to be network accessible
# second address is needed for SSL which is mandatory for remote access
agama-web-server serve --address :::80 --address2 :::443
agama-web-server serve --address :::80 --address :::443

# frontend machine
# ESLINT=0 is useful to ignore linter problems during development
Expand Down
Loading