Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into ui_locale_dbus
Browse files Browse the repository at this point in the history
  • Loading branch information
jreidinger committed Aug 28, 2023
2 parents 117f1ab + e6aff50 commit b8da347
Show file tree
Hide file tree
Showing 27 changed files with 643 additions and 86 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/weblate-merge-po.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ jobs:
- name: Validate the PO files
working-directory: ./agama
run: msgfmt --check-format -o /dev/null web/po/*.po
run: ls web/po/*.po | xargs -n1 msgfmt --check-format -o /dev/null

# any changes besides the timestamps in the PO files?
- name: Check changes
Expand Down
46 changes: 13 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ communication.

![Architecture](./doc/images/architecture.png)

Agama consists on a set of D-Bus services and a web client (an experimental CLI is also available). The services use YaST-based libraries under the hood, reusing a lot logic already provided by YaST. Currently Agama comes with six separate services, although the list can increase in the future:
Agama consists on a set of D-Bus services, a web client and a command-line interface. The services use YaST-based libraries under the hood, reusing a lot logic already provided by YaST. Currently Agama comes with six separate services, although the list can increase in the future:

* Agama service: it is the main service which manages and controls the installation process.
* Software service: configures the product and software to install.
Expand Down Expand Up @@ -136,50 +136,30 @@ Then point your browser to http://localhost:9090/cockpit/@localhost/agama/index.

The [setup.sh](./setup.sh) script installs the required dependencies
to build and run the project and it also configures the Agama services
and cockpit. It uses `sudo` to install packages and files to system locations.
and Cockpit. It uses `sudo` to install packages and files to system locations.
The script is well commented so we refer you to it instead of repeating its
steps here.

Alternatively you can run a development server which works as a proxy for
the cockpit server. See more details [in the documentation](
web/README.md#using-a-development-server).
Regarding the web user interface, alternatively you can run a development
server which works as a proxy for the cockpit server. See more details [in the
documentation]( web/README.md#using-a-development-server).

Another alternative is to run source checkout inside container so system is not
affected by doing testing run beside real actions really done by installer.
See more details [in the documentation](doc/testing_using_container.md).

* Start the services:
* beware that Agama must run as root (like YaST does) to do
hardware probing, partition the disks, install the software and so on.
* Note that `setup.sh` sets up D-Bus activation so starting manually is
only needed when you prefer to see the log output upfront.
To start or stop Agama D-Bus services at any time, use the `agama` systemd service:

```console
$ cd service
$ sudo bundle exec bin/agama
sudo systemctl start agama
```

* Check that Agama services are working with a tool like
[busctl](https://www.freedesktop.org/wiki/Software/dbus/) or
[D-Feet](https://wiki.gnome.org/Apps/DFeet) if you prefer a graphical one:

If something goes wrong, you can use `journalctl` to get Agama logs:

```console
$ busctl --address=unix:path=/run/agama/bus \
call \
org.opensuse.Agama1 \
/org/opensuse/Agama1/Manager \
org.opensuse.Agama1.Manager \
CanInstall

$ busctl --address=unix:path=/run/agama/bus \
call \
org.opensuse.Agama.Locale1 \
/org/opensuse/Agama/Locale1 \
org.freedesktop.DBus.Properties \
GetAll s org.opensuse.Agama.Locale1
sudo journalctl -u agama
```

Another alternative is to run source checkout inside container so system is not
affected by doing testing run beside real actions really done by installer.
See more details [in the documentation](doc/testing_using_container.md).

## How to Contribute

If you want to contribute to Agama, then please open a pull request or report an issue. You can also have a look to our [road-map](https://github.com/orgs/yast/projects/1/views/1).
Expand Down
42 changes: 42 additions & 0 deletions doc/startup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Agama Startup Process

This document summarizes Agama's startup process.

## Overview

As described in the [README](../README.md#architecture), Agama is composed of a set of D-Bus
services, a web client and a command-line interface. The startup process aims to get those D-Bus
services up and running and make the web UI available. Additionally, the auto-installation procedure
could be started if required by the user.

The startup process is handled by systemd and D-Bus. The only exception is starting the local
browser in the Agama Live image.

## Starting the D-Bus Services

[agama.service](../service/share/agama.service) is responsible for starting up Agama's D-Bus daemon
process using the `agamactl --daemon` command. This process uses a dedicated bus with a [specific
configuration](../service/share/dbus.conf).

Once the daemon process is running, each D-Bus service will be automatically activated when
required. The definitions of those services are located in `/usr/share/dbus-1/agama-services`,
although you can find the sources in the [repository](../service/share)
(`org.opensuse.Agama*.service` files).

## Auto-installation

If the `agama.auto` option is specified in the kernel command-line, the
[agama-auto.service](../service/share/systemd/agama-auto.service) comes into play. It runs after the
`agama.service` so the D-Bus daemon is ready and the services can be activated as needed.

## Web UI

When discussing the web UI, we can distinguish two sides: the server process and the web browser.
Regarding the server, Agama's web UI is implemented as a Cockpit module, so the only requirement is
that the `cockpit.socket` is enabled. Then, you can connect to the UI using the
`https://$SERVER_IP:9090/cockpit/@localhost/agama/index.html`.

When using Agama Live, a local web browser is automatically started. In the default image, it is
launched using an Icewm startup script[^1].

[^1]: Check the `root.tar` file from the [agama-live](https://build.opensuse.org/package/show/systemsmanagement:Agama:Devel/agama-live) sources.
4 changes: 2 additions & 2 deletions rust/agama-dbus-server/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use agama_dbus_server::{locale, network::NetworkService, questions};
use agama_dbus_server::{locale, network, questions};

use log::LevelFilter;
use std::future::pending;
Expand Down Expand Up @@ -28,7 +28,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
log::info!("Started questions interface");
let _conn = locale::start_service(ADDRESS).await?;
log::info!("Started locale interface");
NetworkService::start(ADDRESS).await?;
network::start_service(ADDRESS).await?;
log::info!("Started network interface");

// Do other things or go to wait forever
Expand Down
8 changes: 8 additions & 0 deletions rust/agama-dbus-server/src/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,12 @@ pub use action::Action;
pub use adapter::Adapter;
pub use dbus::NetworkService;
pub use model::NetworkState;
pub use nm::NetworkManagerAdapter;
pub use system::NetworkSystem;

pub async fn start_service(address: &str) -> Result<(), Box<dyn std::error::Error>> {
let adapter = NetworkManagerAdapter::from_system()
.await
.expect("Could not connect to NetworkManager to read the configuration.");
NetworkService::start(address, adapter).await
}
10 changes: 9 additions & 1 deletion rust/agama-dbus-server/src/network/dbus/interfaces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use std::net::{AddrParseError, Ipv4Addr};
use zbus::{
dbus_interface,
zvariant::{ObjectPath, OwnedObjectPath},
SignalContext,
};

/// D-Bus interface for the network devices collection
Expand Down Expand Up @@ -126,7 +127,7 @@ impl Connections {
pub async fn add_connection(&mut self, id: String, ty: u8) -> zbus::fdo::Result<()> {
let actions = self.actions.lock().await;
actions
.send(Action::AddConnection(id, ty.try_into()?))
.send(Action::AddConnection(id.clone(), ty.try_into()?))
.await
.unwrap();
Ok(())
Expand Down Expand Up @@ -163,6 +164,13 @@ impl Connections {
actions.send(Action::Apply).await.unwrap();
Ok(())
}

#[dbus_interface(signal)]
pub async fn connection_added(
ctxt: &SignalContext<'_>,
id: &str,
path: &str,
) -> zbus::Result<()>;
}

/// D-Bus interface for a network connection
Expand Down
11 changes: 6 additions & 5 deletions rust/agama-dbus-server/src/network/dbus/service.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Network D-Bus service.
//!
//! This module defines a D-Bus service which exposes Agama's network configuration.
use crate::network::NetworkSystem;
use crate::network::{Adapter, NetworkSystem};
use agama_lib::connection_to;
use std::error::Error;

Expand All @@ -12,13 +12,14 @@ pub struct NetworkService;

impl NetworkService {
/// Starts listening and dispatching events on the D-Bus connection.
pub async fn start(address: &str) -> Result<(), Box<dyn Error>> {
pub async fn start<T: Adapter + std::marker::Send + 'static>(
address: &str,
adapter: T,
) -> Result<(), Box<dyn Error>> {
const SERVICE_NAME: &str = "org.opensuse.Agama.Network1";

let connection = connection_to(address).await?;
let mut network = NetworkSystem::from_network_manager(connection.clone())
.await
.expect("Could not read network state");
let mut network = NetworkSystem::new(connection.clone(), adapter);

async_std::task::spawn(async move {
network
Expand Down
25 changes: 22 additions & 3 deletions rust/agama-dbus-server/src/network/dbus/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,15 @@ impl Tree {
/// Adds a connection to the D-Bus tree.
///
/// * `connection`: connection to add.
pub async fn add_connection(&self, conn: &mut Connection) -> Result<(), ServiceError> {
/// * `notify`: whether to notify the added connection
pub async fn add_connection(
&self,
conn: &mut Connection,
notify: bool,
) -> Result<(), ServiceError> {
let mut objects = self.objects.lock().await;

let orig_id = conn.id().to_owned();
let (id, path) = objects.register_connection(conn);
if id != conn.id() {
conn.set_id(&id)
Expand All @@ -106,6 +112,10 @@ impl Tree {
.await?;
}

if notify {
self.notify_connection_added(&orig_id, &path).await?;
}

Ok(())
}

Expand All @@ -115,7 +125,7 @@ impl Tree {
pub async fn remove_connection(&mut self, id: &str) -> Result<(), ServiceError> {
let mut objects = self.objects.lock().await;
let Some(path) = objects.connection_path(id) else {
return Ok(())
return Ok(());
};
self.remove_connection_on(path.as_str()).await?;
objects.deregister_connection(id).unwrap();
Expand All @@ -127,7 +137,7 @@ impl Tree {
/// * `connections`: list of connections.
async fn add_connections(&self, connections: &mut [Connection]) -> Result<(), ServiceError> {
for conn in connections.iter_mut() {
self.add_connection(conn).await?;
self.add_connection(conn, false).await?;
}

self.add_interface(
Expand Down Expand Up @@ -182,6 +192,15 @@ impl Tree {
let object_server = self.connection.object_server();
Ok(object_server.at(path, iface).await?)
}

/// Notify that a new connection has been added
async fn notify_connection_added(&self, id: &str, path: &str) -> Result<(), ServiceError> {
let object_server = self.connection.object_server();
let iface_ref = object_server
.interface::<_, interfaces::Connections>(CONNECTIONS_PATH)
.await?;
Ok(interfaces::Connections::connection_added(iface_ref.signal_context(), id, path).await?)
}
}

/// Objects paths for known devices and connections
Expand Down
2 changes: 1 addition & 1 deletion rust/agama-dbus-server/src/network/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use std::{
};
use thiserror::Error;

#[derive(Default)]
#[derive(Default, Clone)]
pub struct NetworkState {
pub devices: Vec<Device>,
pub connections: Vec<Connection>,
Expand Down
50 changes: 17 additions & 33 deletions rust/agama-dbus-server/src/network/system.rs
Original file line number Diff line number Diff line change
@@ -1,53 +1,36 @@
use crate::network::{
dbus::Tree, model::Connection, nm::NetworkManagerAdapter, Action, Adapter, NetworkState,
};
use agama_lib::error::ServiceError;
use crate::network::{dbus::Tree, model::Connection, Action, Adapter, NetworkState};
use async_std::channel::{unbounded, Receiver, Sender};
use std::error::Error;

/// Represents the network system, wrapping a [NetworkState] and setting up the D-Bus tree.
pub struct NetworkSystem {
/// Represents the network system using holding the state and setting up the D-Bus tree.
pub struct NetworkSystem<T: Adapter> {
/// Network state
pub state: NetworkState,
/// Side of the channel to send actions.
actions_tx: Sender<Action>,
actions_rx: Receiver<Action>,
tree: Tree,
/// Adapter to read/write the network state.
adapter: T,
}

impl NetworkSystem {
pub fn new(state: NetworkState, conn: zbus::Connection) -> Self {
impl<T: Adapter> NetworkSystem<T> {
pub fn new(conn: zbus::Connection, adapter: T) -> Self {
let (actions_tx, actions_rx) = unbounded();
let tree = Tree::new(conn, actions_tx.clone());
Self {
state,
state: NetworkState::default(),
actions_tx,
actions_rx,
tree,
adapter,
}
}

/// Reads the network configuration using the NetworkManager adapter.
///
/// * `conn`: connection where self will be exposed. Another connection will be made internally
/// to talk with NetworkManager (which may be on a different bus even).
pub async fn from_network_manager(
conn: zbus::Connection,
) -> Result<NetworkSystem, Box<dyn Error>> {
let adapter = NetworkManagerAdapter::from_system()
.await
.expect("Could not connect to NetworkManager to read the configuration.");
let state = adapter.read()?;
Ok(Self::new(state, conn))
}

/// Writes the network configuration to NetworkManager.
pub async fn to_network_manager(&mut self) -> Result<(), Box<dyn Error>> {
let adapter = NetworkManagerAdapter::from_system()
.await
.expect("Could not connect to NetworkManager to write the changes.");
adapter.write(&self.state)?;
self.state = adapter.read()?;
/// Writes the network configuration.
pub async fn write(&mut self) -> Result<(), Box<dyn Error>> {
self.adapter.write(&self.state)?;
self.state = self.adapter.read()?;
Ok(())
}

Expand All @@ -58,7 +41,8 @@ impl NetworkSystem {
}

/// Populates the D-Bus tree with the known devices and connections.
pub async fn setup(&mut self) -> Result<(), ServiceError> {
pub async fn setup(&mut self) -> Result<(), Box<dyn Error>> {
self.state = self.adapter.read()?;
self.tree
.set_connections(&mut self.state.connections)
.await?;
Expand All @@ -82,7 +66,7 @@ impl NetworkSystem {
match action {
Action::AddConnection(name, ty) => {
let mut conn = Connection::new(name, ty);
self.tree.add_connection(&mut conn).await?;
self.tree.add_connection(&mut conn, true).await?;
self.state.add_connection(conn)?;
}
Action::UpdateConnection(conn) => {
Expand All @@ -93,7 +77,7 @@ impl NetworkSystem {
self.state.remove_connection(&id)?;
}
Action::Apply => {
self.to_network_manager().await?;
self.write().await?;
// TODO: re-creating the tree is kind of brute-force and it sends signals about
// adding/removing interfaces. We should add/update/delete objects as needed.
self.tree
Expand Down
Loading

0 comments on commit b8da347

Please sign in to comment.