diff --git a/configuration/init/systemd/75-tedge.preset b/configuration/init/systemd/75-tedge.preset index c18f06e6edd..fa8d5365f88 100644 --- a/configuration/init/systemd/75-tedge.preset +++ b/configuration/init/systemd/75-tedge.preset @@ -5,6 +5,7 @@ # # Device management enable c8y-firmware-plugin.service +enable c8y-remote-access-plugin.socket # Agent disable tedge-agent.service diff --git a/configuration/init/systemd/c8y-remote-access-plugin.socket b/configuration/init/systemd/c8y-remote-access-plugin.socket new file mode 100644 index 00000000000..dcc0581d890 --- /dev/null +++ b/configuration/init/systemd/c8y-remote-access-plugin.socket @@ -0,0 +1,13 @@ +[Unit] +Description=c8y-remote-access-plugin socket +PartOf=c8y-remote-access-plugin.service + +[Socket] +ListenStream=/run/c8y-remote-access-plugin.sock +SocketMode=0660 +SocketUser=tedge +SocketGroup=tedge +Accept=yes + +[Install] +WantedBy=sockets.target diff --git a/configuration/init/systemd/c8y-remote-access-plugin@.service b/configuration/init/systemd/c8y-remote-access-plugin@.service new file mode 100644 index 00000000000..7e0a76c9f11 --- /dev/null +++ b/configuration/init/systemd/c8y-remote-access-plugin@.service @@ -0,0 +1,12 @@ +[Unit] +Description=c8y-remote-access-plugin Service +After=network.target c8y-remote-access-plugin.socket +Requires=c8y-remote-access-plugin.socket +CollectMode=inactive-or-failed + +[Service] +ExecStart=/usr/bin/c8y-remote-access-plugin --child - +StandardInput=socket + +[Install] +WantedBy=default.target diff --git a/configuration/package_manifests/nfpm.c8y-remote-access-plugin.yaml b/configuration/package_manifests/nfpm.c8y-remote-access-plugin.yaml index c436d591500..9d064916576 100644 --- a/configuration/package_manifests/nfpm.c8y-remote-access-plugin.yaml +++ b/configuration/package_manifests/nfpm.c8y-remote-access-plugin.yaml @@ -23,6 +23,32 @@ deb: depends: - tedge +contents: +# service definitions + - src: ./configuration/init/systemd/c8y-remote-access-plugin.socket + dst: /lib/systemd/system/c8y-remote-access-plugin.socket + file_info: + mode: 0644 + packager: deb + + - src: ./configuration/init/systemd/c8y-remote-access-plugin.socket + dst: /lib/systemd/system/c8y-remote-access-plugin.socket + file_info: + mode: 0644 + packager: rpm + + - src: ./configuration/init/systemd/c8y-remote-access-plugin@.service + dst: /lib/systemd/system/c8y-remote-access-plugin@.service + file_info: + mode: 0644 + packager: deb + + - src: ./configuration/init/systemd/c8y-remote-access-plugin@.service + dst: /lib/systemd/system/c8y-remote-access-plugin@.service + file_info: + mode: 0644 + packager: rpm + overrides: apk: scripts: diff --git a/configuration/package_scripts/_generated/c8y-remote-access-plugin/apk/postinst b/configuration/package_scripts/_generated/c8y-remote-access-plugin/apk/postinst index e3c54617ed8..43c60b9e8e9 100644 --- a/configuration/package_scripts/_generated/c8y-remote-access-plugin/apk/postinst +++ b/configuration/package_scripts/_generated/c8y-remote-access-plugin/apk/postinst @@ -1,5 +1,8 @@ #!/bin/sh set -e + + + ### Create supported operation files c8y-remote-access-plugin --init diff --git a/configuration/package_scripts/_generated/c8y-remote-access-plugin/apk/postrm b/configuration/package_scripts/_generated/c8y-remote-access-plugin/apk/postrm index d37118bbe57..aa2813997b3 100644 --- a/configuration/package_scripts/_generated/c8y-remote-access-plugin/apk/postrm +++ b/configuration/package_scripts/_generated/c8y-remote-access-plugin/apk/postrm @@ -1,2 +1,3 @@ #!/bin/sh set -e + diff --git a/configuration/package_scripts/_generated/c8y-remote-access-plugin/apk/prerm b/configuration/package_scripts/_generated/c8y-remote-access-plugin/apk/prerm index 7f7d0a049f0..21b24189904 100644 --- a/configuration/package_scripts/_generated/c8y-remote-access-plugin/apk/prerm +++ b/configuration/package_scripts/_generated/c8y-remote-access-plugin/apk/prerm @@ -1,5 +1,7 @@ #!/bin/sh set -e + + ### Remove supported operation files c8y-remote-access-plugin --cleanup diff --git a/configuration/package_scripts/_generated/c8y-remote-access-plugin/deb/postinst b/configuration/package_scripts/_generated/c8y-remote-access-plugin/deb/postinst index e3c54617ed8..fb8b998cdca 100755 --- a/configuration/package_scripts/_generated/c8y-remote-access-plugin/deb/postinst +++ b/configuration/package_scripts/_generated/c8y-remote-access-plugin/deb/postinst @@ -1,5 +1,31 @@ #!/bin/sh set -e +# Automatically added by thin-edge.io +if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then + # This will only remove masks created by d-s-h on package removal. + deb-systemd-helper unmask c8y-remote-access-plugin.socket >/dev/null || true + + # was-enabled defaults to true, so new installations run enable. + if deb-systemd-helper --quiet was-enabled c8y-remote-access-plugin.socket; then + # Enables the unit on first installation, creates new + # symlinks on upgrades if the unit file has changed. + deb-systemd-helper enable c8y-remote-access-plugin.socket >/dev/null || true + else + # Update the statefile to add new symlinks (if any), which need to be + # cleaned up on purge. Also remove old symlinks. + deb-systemd-helper update-state c8y-remote-access-plugin.socket >/dev/null || true + fi +fi +# End automatically added section +# Automatically added by thin-edge.io +if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then + if [ -d /run/systemd/system ]; then + systemctl --system daemon-reload >/dev/null || true + deb-systemd-invoke start c8y-remote-access-plugin.socket >/dev/null || true + fi +fi +# End automatically added section + ### Create supported operation files c8y-remote-access-plugin --init diff --git a/configuration/package_scripts/_generated/c8y-remote-access-plugin/deb/postrm b/configuration/package_scripts/_generated/c8y-remote-access-plugin/deb/postrm index d37118bbe57..be8586b8664 100644 --- a/configuration/package_scripts/_generated/c8y-remote-access-plugin/deb/postrm +++ b/configuration/package_scripts/_generated/c8y-remote-access-plugin/deb/postrm @@ -1,2 +1,21 @@ #!/bin/sh set -e +# Automatically added by thin-edge.io +if [ -d /run/systemd/system ]; then + systemctl --system daemon-reload >/dev/null || true +fi +# End automatically added section +# Automatically added by thin-edge.io +if [ "$1" = "remove" ]; then + if [ -x "/usr/bin/deb-systemd-helper" ]; then + deb-systemd-helper mask c8y-remote-access-plugin.socket >/dev/null || true + fi +fi + +if [ "$1" = "purge" ]; then + if [ -x "/usr/bin/deb-systemd-helper" ]; then + deb-systemd-helper purge c8y-remote-access-plugin.socket >/dev/null || true + deb-systemd-helper unmask c8y-remote-access-plugin.socket >/dev/null || true + fi +fi +# End automatically added section \ No newline at end of file diff --git a/configuration/package_scripts/_generated/c8y-remote-access-plugin/deb/prerm b/configuration/package_scripts/_generated/c8y-remote-access-plugin/deb/prerm index 7f7d0a049f0..a6124c49652 100755 --- a/configuration/package_scripts/_generated/c8y-remote-access-plugin/deb/prerm +++ b/configuration/package_scripts/_generated/c8y-remote-access-plugin/deb/prerm @@ -1,5 +1,11 @@ #!/bin/sh set -e +# Automatically added by thin-edge.io +if [ -d /run/systemd/system ] && [ "$1" = remove ]; then + deb-systemd-invoke stop c8y-remote-access-plugin.socket >/dev/null || true +fi +# End automatically added section + ### Remove supported operation files c8y-remote-access-plugin --cleanup diff --git a/configuration/package_scripts/_generated/c8y-remote-access-plugin/rpm/postinst b/configuration/package_scripts/_generated/c8y-remote-access-plugin/rpm/postinst index e3c54617ed8..78138bca8b4 100644 --- a/configuration/package_scripts/_generated/c8y-remote-access-plugin/rpm/postinst +++ b/configuration/package_scripts/_generated/c8y-remote-access-plugin/rpm/postinst @@ -1,5 +1,18 @@ #!/bin/sh set -e +# Automatically added by thin-edge.io +if [ $1 -eq 1 ] && [ -x "/usr/lib/systemd/systemd-update-helper" ]; then + # Initial installation + /usr/lib/systemd/systemd-update-helper install-system-units c8y-remote-access-plugin.socket || : +fi +# End automatically added section +# Automatically added by thin-edge.io +if [ -d /run/systemd/system ]; then + systemctl --system daemon-reload >/dev/null || true + systemctl start c8y-remote-access-plugin.socket >/dev/null || true +fi +# End automatically added section + ### Create supported operation files c8y-remote-access-plugin --init diff --git a/configuration/package_scripts/_generated/c8y-remote-access-plugin/rpm/postrm b/configuration/package_scripts/_generated/c8y-remote-access-plugin/rpm/postrm index d37118bbe57..981c9ac9221 100644 --- a/configuration/package_scripts/_generated/c8y-remote-access-plugin/rpm/postrm +++ b/configuration/package_scripts/_generated/c8y-remote-access-plugin/rpm/postrm @@ -1,2 +1,7 @@ #!/bin/sh set -e +# Automatically added by thin-edge.io +if [ -d /run/systemd/system ]; then + systemctl --system daemon-reload >/dev/null || true +fi +# End automatically added section \ No newline at end of file diff --git a/configuration/package_scripts/_generated/c8y-remote-access-plugin/rpm/prerm b/configuration/package_scripts/_generated/c8y-remote-access-plugin/rpm/prerm index 7f7d0a049f0..e78c925f2fa 100644 --- a/configuration/package_scripts/_generated/c8y-remote-access-plugin/rpm/prerm +++ b/configuration/package_scripts/_generated/c8y-remote-access-plugin/rpm/prerm @@ -1,5 +1,12 @@ #!/bin/sh set -e +# Automatically added by thin-edge.io +if [ $1 -eq 0 ] && [ -x "/usr/lib/systemd/systemd-update-helper" ]; then + # Package removal, not upgrade + /usr/lib/systemd/systemd-update-helper remove-system-units c8y-remote-access-plugin.socket || : +fi +# End automatically added section + ### Remove supported operation files c8y-remote-access-plugin --cleanup diff --git a/configuration/package_scripts/c8y-remote-access-plugin/postinst b/configuration/package_scripts/c8y-remote-access-plugin/postinst index e3c54617ed8..f4690e399b2 100644 --- a/configuration/package_scripts/c8y-remote-access-plugin/postinst +++ b/configuration/package_scripts/c8y-remote-access-plugin/postinst @@ -1,5 +1,7 @@ #!/bin/sh set -e +#LINUXHELPER# + ### Create supported operation files c8y-remote-access-plugin --init diff --git a/configuration/package_scripts/c8y-remote-access-plugin/prerm b/configuration/package_scripts/c8y-remote-access-plugin/prerm index 7f7d0a049f0..75af4cd8bec 100644 --- a/configuration/package_scripts/c8y-remote-access-plugin/prerm +++ b/configuration/package_scripts/c8y-remote-access-plugin/prerm @@ -1,5 +1,7 @@ #!/bin/sh set -e +#LINUXHELPER# + ### Remove supported operation files c8y-remote-access-plugin --cleanup diff --git a/configuration/package_scripts/generate.py b/configuration/package_scripts/generate.py index beeea29dc6d..412a03cc95c 100755 --- a/configuration/package_scripts/generate.py +++ b/configuration/package_scripts/generate.py @@ -104,6 +104,11 @@ def write_script( output_file.write_text(contents, encoding="utf8") return contents +def format_unit_name(name: str, default_suffix = ".service") -> str: + if "." not in name: + return name + default_suffix + return name + def process_package(name: str, manifest: dict, package_type: str, out_dir: Path): services = [Service(**service) for service in manifest.get("services", [])] @@ -113,7 +118,7 @@ def process_package(name: str, manifest: dict, package_type: str, out_dir: Path) prerm = [] postrm = [] - service_names = [(service.name or name) + ".service" for service in services] + service_names = [format_unit_name((service.name or name), ".service") for service in services] log.info("Processing package: %s, type=%s", name, package_type) variables = { @@ -123,7 +128,7 @@ def process_package(name: str, manifest: dict, package_type: str, out_dir: Path) service = None for service in services: - service_name = (service.name or name) + ".service" + service_name = format_unit_name((service.name or name), ".service") log.info( "Processing service: package=%s, service=%s, type=%s", name, diff --git a/configuration/package_scripts/packages.json b/configuration/package_scripts/packages.json index 21b612764af..8bab1ea5dee 100644 --- a/configuration/package_scripts/packages.json +++ b/configuration/package_scripts/packages.json @@ -6,7 +6,11 @@ ], "packages": { "tedge": {}, - "c8y-remote-access-plugin": {}, + "c8y-remote-access-plugin": { + "services": [ + {"name": "c8y-remote-access-plugin.socket", "enable": true, "start": true, "restart_after_upgrade": false, "stop_on_upgrade": false} + ] + }, "tedge-agent": { "services": [ // Don't stop or restart service when upgrading as the old agent diff --git a/plugins/c8y_remote_access_plugin/src/input.rs b/plugins/c8y_remote_access_plugin/src/input.rs index 760fedbcb5e..85a53fa953d 100644 --- a/plugins/c8y_remote_access_plugin/src/input.rs +++ b/plugins/c8y_remote_access_plugin/src/input.rs @@ -7,10 +7,12 @@ use serde::Deserialize; use std::io::stdin; use std::io::BufRead; use std::path::PathBuf; +use tedge_config::Path; use tedge_config::TEdgeConfigLocation; use tedge_config::DEFAULT_TEDGE_CONFIG_PATH; use crate::csv::deserialize_csv_record; +use crate::UNIX_SOCKFILE; #[derive(Parser, Deserialize, Debug, PartialEq, Eq)] pub struct RemoteAccessConnect { @@ -63,6 +65,7 @@ pub enum Command { Init, Cleanup, SpawnChild(String), + TryConnectUnixSocket(String), Connect(RemoteAccessConnect), } @@ -79,7 +82,13 @@ impl TryFrom for Command { C8yRemoteAccessPluginOpt { connect_string: Some(message), .. - } => Ok(Command::SpawnChild(message)), + } => { + if Path::new(UNIX_SOCKFILE).exists() { + Ok(Command::TryConnectUnixSocket(message)) + } else { + Ok(Command::SpawnChild(message)) + } + } C8yRemoteAccessPluginOpt { child: Some(message), .. diff --git a/plugins/c8y_remote_access_plugin/src/lib.rs b/plugins/c8y_remote_access_plugin/src/lib.rs index a6105df879b..7b6e9e2b464 100644 --- a/plugins/c8y_remote_access_plugin/src/lib.rs +++ b/plugins/c8y_remote_access_plugin/src/lib.rs @@ -6,12 +6,15 @@ use input::parse_arguments; use miette::miette; use miette::Context; use miette::IntoDiagnostic; +use std::io; use std::process::Stdio; use tedge_config::TEdgeConfig; use tedge_utils::file::create_directory_with_user_group; use tedge_utils::file::create_file_with_user_group; use tokio::io::AsyncBufReadExt; +use tokio::io::AsyncWriteExt; use tokio::io::BufReader; +use tokio::net::UnixStream; use url::Url; use crate::auth::Jwt; @@ -25,6 +28,8 @@ mod csv; mod input; mod proxy; +const UNIX_SOCKFILE: &str = "/run/c8y-remote-access-plugin.sock"; + pub async fn run(opt: C8yRemoteAccessPluginOpt) -> miette::Result<()> { let config_dir = opt.get_config_location(); @@ -45,6 +50,20 @@ pub async fn run(opt: C8yRemoteAccessPluginOpt) -> miette::Result<()> { } Command::Connect(command) => proxy(command, tedge_config).await, Command::SpawnChild(command) => spawn_child(command, config_dir.tedge_config_root_path()).await, + Command::TryConnectUnixSocket(command) => { + match UnixStream::connect(UNIX_SOCKFILE).await { + Ok(mut unix_stream) => { + eprintln!("sock: Connected to Unix socket at {UNIX_SOCKFILE}"); + write_request_and_shutdown(&mut unix_stream, command).await?; + read_from_stream(&mut unix_stream).await?; + Ok(()) + } + Err(_e) => { + eprintln!("sock: Could not connect to Unix socket at {UNIX_SOCKFILE}. Falling back to spawning a child process"); + spawn_child(command, config_dir.tedge_config_root_path()).await + } + } + }, } } @@ -154,6 +173,84 @@ async fn spawn_child(command: String, config_dir: &Utf8Path) -> miette::Result<( } } +#[derive(miette::Diagnostic, Debug, thiserror::Error)] +#[error("Failed while {1}")] +#[diagnostic(help("Check if Unix Socket is readable and writable."))] +struct UnixSocketError(#[source] E, &'static str); + +async fn write_request_and_shutdown( + unix_stream: &mut UnixStream, + command: String, +) -> miette::Result<()> { + unix_stream + .writable() + .await + .into_diagnostic() + .context("sock: Socket is not writable")?; + + eprintln!("sock: Writing message ({command}) to socket"); + unix_stream + .write_all(command.as_bytes()) + .await + .into_diagnostic() + .context("sock: Could not write to socket")?; + eprintln!("sock: Message sent"); + + eprintln!("sock: Shutting down writing on the stream, waiting for response..."); + unix_stream + .shutdown() + .await + .into_diagnostic() + .context("sock: Could not shutdown writing on the stream")?; + eprintln!("sock: Shut down successful"); + + Ok(()) +} + +async fn read_from_stream(unix_stream: &mut UnixStream) -> miette::Result<()> { + unix_stream + .readable() + .await + .into_diagnostic() + .context("sock: Socket is not readable")?; + + eprintln!("sock: Reading response..."); + let stream = BufReader::new(unix_stream); + let mut lines = stream.lines(); + + while let Ok(maybe_line) = lines.next_line().await { + match maybe_line { + Some(line) => { + eprintln!("sock: Received line: {line}"); + match line.as_str() { + str if str == SUCCESS_MESSAGE => { + eprintln!("sock: Detected successful response"); + return Ok(()); + } + "STOPPING" => { + eprintln!("sock: Detected error response"); + break; + } + _ => continue, + } + } + None => { + eprintln!("sock: Connection closed by peer"); + break; + } + } + } + + Err(UnixSocketError( + io::Error::new( + io::ErrorKind::Other, + format!("sock: Did not receive expected response from socket. Expected = '{SUCCESS_MESSAGE}'"), + ), + "checking the response from the unix socket", + ) + .into()) +} + async fn proxy(command: RemoteAccessConnect, config: TEdgeConfig) -> miette::Result<()> { let host = config .c8y diff --git a/tests/RobotFramework/libraries/ThinEdgeIO/ThinEdgeIO.py b/tests/RobotFramework/libraries/ThinEdgeIO/ThinEdgeIO.py index 17ed379c20f..08c79587939 100644 --- a/tests/RobotFramework/libraries/ThinEdgeIO/ThinEdgeIO.py +++ b/tests/RobotFramework/libraries/ThinEdgeIO/ThinEdgeIO.py @@ -841,10 +841,11 @@ def execute_remote_access_command( proc.wait(timeout) stdout = proc.stdout.read() + stderr = proc.stderr.read() if exp_exit_code is not None: assert ( proc.returncode == exp_exit_code - ), f"Failed to connect via remote access.\n{stdout}" + ), f"Failed to connect via remote access.\nstdout: <