diff --git a/CMakeLists.txt b/CMakeLists.txt index be24f0b0f..24a88b4ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -331,7 +331,7 @@ set ( if (ADUC_UBUNTU_CORE_SNAP_ONLY) set ( ADUC_FILE_USER - "snap_aziot_du" + "root" CACHE STRING "The user for adu file ownership.") set ( diff --git a/docs/agent-reference/how-to-build-deviceupdate-agent-snap.md b/docs/agent-reference/how-to-build-deviceupdate-agent-snap.md index 7ddf90b01..eb24333f7 100644 --- a/docs/agent-reference/how-to-build-deviceupdate-agent-snap.md +++ b/docs/agent-reference/how-to-build-deviceupdate-agent-snap.md @@ -13,11 +13,11 @@ - snapd, snapcraft, and [multipass](https://snapcraft.io/docs/build-options) packages - `snapd` is pre-installed on Ubuntu 20.04 LTS by default. For more information about `snapd`, see https://snapcraft.io/docs/installing-snapd - To install `snapcraft` use following command: - ```sh + ```shell sudo snap install snapcraft --classic ``` - To install `multipass` use following command: - ```sh + ```shell sudo snap install multipass --classic ``` For more infomation about `Multipass`, see https://multipass.run/ @@ -30,7 +30,7 @@ ## Build Instruction -```sh +```shell # change directory to iot-hub-device-update project root directory pushd @@ -45,14 +45,14 @@ snapcraft If build success, you can find `deviceupdate-agent_#.#_amd64.snap` at the project root directory. -### How To View Snap Packge Content +### How To View Snap Package Content You can use `unsquashfs` command to extract `.snap` file. -```sh +```shell # Run unsquashfs # e.g., unsquashfs deviceupdate-agent.0.1_amd64.snap -unsquashfs deviceupdate-agent.0.1_adm64.nsap +unsquashfs deviceupdate-agent.0.1_adm64.snap # The content will be extracted to 'squashfs-root' directory # You can use 'tree' command to view the directory content @@ -63,23 +63,100 @@ tree squashfs-root To install the snap for testing (developer mode) -```sh +```shell sudo snap install --devmode ./deviceupdate-agent_0.1_amd64.snap ``` > NOTE | This will installs the snap content to /snap/<'snap name'>/ directory on the host device +## Configure The Device Update Agent Snap + +Specify an agent settings, such as, manufacturer, model, agent.name, agent.connectionSource, etc., in `du-config.json`, which localted in `$SNAP_DATA/config` folder. + +```json +{ + "schemaVersion": "1.1", + "aduShellTrustedUsers": [ + "snap_aziot_du", + "snap_aziot_do" + ], + "iotHubProtocol": "mqtt", + "manufacturer":"", + "model": "", + "agents": [ + { + "name": "", + "runas": "snap_aziot_du", + "connectionSource": { + "connectionType": "string", + "connectionData": "HostName=...HIDDEN..." + }, + "manufacturer": "", + "model": "" + } + ] +} +``` + +For example: +- "manufacturer":"contoso" +- "model":"vacuum-v1" +- "agents[0].name":"main" +- "agents[0].connectionSource.connectionType":"string" +- "agents[0].connectionSource.connectionData":"**enter your device connection string here**" +- "agents[0].manufacturer":"contoso" +- "agents[0].model":"vacuum-v1" + +>TIP: To share file between host machine and the snap, you can use a ['home' interface](https://snapcraft.io/docs/home-interface). + +- To share 'home' directory with the snap, run: + ```shell + $ snap connect deviceupdate-agent:home :home + ``` +- Create du-config.json file in your home directory like so: + ```json + { + "schemaVersion": "1.1", + "aduShellTrustedUsers": [ + "snap_aziot_du", + "snap_aziot_do" + ], + "iotHubProtocol": "mqtt", + "manufacturer": "contoso", + "model": "vacuum-v1", + "agents": [ + { + "name": "main", + "runas": "snap_aziot_du", + "connectionSource": { + "connectionType": "string", + "connectionData": "HostName=...HIDDEN..." + }, + "manufacturer": "contoso", + "model": "vacuum-v1" + } + ] + } + ``` + + ## Run The Snap -To run a shell in the confined environment +To run a shell in the confined environment: + +```shell +$ sudo snap run --shell deviceupdate-agent +``` + +Optionally, copy the prepared `du-config.json` from [host machine] home directory to `$SNAP_DATA/config` directory -```sh - snap run --shell deviceupdate-agent +```shell +$ cp /du-config.json $SNAP_DATA/config ``` Inspect some snap variables: -```sh +```shell root@myhost:~# printenv SNAP /snap/deviceupdate-agent/x2 @@ -90,19 +167,145 @@ root@myhost:~# printenv SNAP_DATA /var/snap/deviceupdate-agent/x2 ``` +For testing purposes, you can manually run `$SNAP/usr/bin/AducIotAgent -l 0 -e` to verify that the Device Update agent is successfully connected to, and communicated with, the Azure Iot Hub. + +```shell +$SNAP/usr/bin/AducIotAgent -l 0 -e + +2022-11-30T15:19:17.5333Z 33853[33853] [D] Log file created: /var/log/adu/du-agent.20221130-151917.log [zlog_init] +2022-11-30T15:19:17.5338Z 33853[33853] [I] Agent (linux; 1.0.0) starting. [main] + +... + +2022-11-30T15:19:17.5339Z 33853[33853] [I] Supported Update Manifest version: min: 4, max: 5 [main] +2022-11-30T15:19:17.5340Z 33853[33853] [I] Attempting to create connection to IotHub using type: ADUC_ConnType_Device [ADUC_DeviceClient_Create] +2022-11-30T15:19:17.5341Z 33853[33853] [I] IotHub Protocol: MQTT [GetIotHubProtocolFromConfig] +2022-11-30T15:19:17.5341Z 33853[33853] [I] IoTHub Device Twin callback registered. [ADUC_DeviceClient_Create] +2022-11-30T15:19:17.5341Z 33853[33853] [I] Initializing PnP components. [ADUC_PnP_Components_Create] +2022-11-30T15:19:17.5341Z 33853[33853] [I] ADUC agent started. Using IoT Hub Client SDK 1.7.0 [AzureDeviceUpdateCoreInterface_Create] +2022-11-30T15:19:17.5341Z 33853[33853] [I] Calling ADUC_RegisterPlatformLayer [ADUC_MethodCall_Register] + +... + +2022-11-30T15:19:17.5702Z 33853[33853] [I] Agent running. [main] + +... + +2022-11-30T15:19:18.1976Z 33853[33853] [D] IotHub connection status: 0, reason: 6 [ADUC_ConnectionStatus_Callback] +-> 15:19:18 SUBSCRIBE | PACKET_ID: 2 | TOPIC_NAME: $iothub/twin/res/# | QOS: 0 + +... + +2022-11-30T15:19:18.5998Z 33853[33853] [I] Processing existing Device Twin data after agent started. [ADUC_PnPDeviceTwin_Callback] +2022-11-30T15:19:18.5999Z 33853[33853] [D] Notifies components that all callback are subscribed. [ADUC_PnPDeviceTwin_Callback] + +... + +2022-11-30T15:19:18.6001Z 33853[33853] [D] Queueing message (t:0, c:0xbb236af0 [ADUC_D2C_Message_SendAsync] +2022-11-30T15:19:18.6002Z 33853[33853] [I] DeviceInformation component is ready - reporting properties [DeviceInfoInterface_Connected] +2022-11-30T15:19:18.6003Z 33853[33853] [I] Property manufacturer changed to contoso [RefreshDeviceInfoInterfaceData] +2022-11-30T15:19:18.6004Z 33853[33853] [I] Property model changed to vacuum-v1 [RefreshDeviceInfoInterfaceData] +2022-11-30T15:19:18.6006Z 33853[33853] [I] Property osName changed to Ubuntu Core [RefreshDeviceInfoInterfaceData] +2022-11-30T15:19:18.6007Z 33853[33853] [I] Property swVersion changed to 20 [RefreshDeviceInfoInterfaceData] +2022-11-30T15:19:18.6007Z 33853[33853] [I] Property processorArchitecture changed to x86_64 [RefreshDeviceInfoInterfaceData] +2022-11-30T15:19:18.6242Z 33853[33853] [I] Property processorManufacturer changed to GenuineIntel [RefreshDeviceInfoInterfaceData] +2022-11-30T15:19:18.6243Z 33853[33853] [I] Property totalMemory changed to 5776704 [RefreshDeviceInfoInterfaceData] +2022-11-30T15:19:18.6244Z 33853[33853] [I] Property totalStorage changed to 64768 [RefreshDeviceInfoInterfaceData] +2022-11-30T15:19:18.6245Z 33853[33853] [D] Queueing message (t:2, c:0xbb2306a0 [ADUC_D2C_Message_SendAsync] +2022-11-30T15:19:18.6245Z 33853[33853] [I] DiagnosticsInterface is connected [DiagnosticsInterface_Connected] +2022-11-30T15:19:18.7248Z 33853[33853] [D] Sending D2C message (t:0, retries:0). [ProcessMessage] +2022-11-30T15:19:18.7250Z 33853[33853] [D] Sending D2C message: +{"deviceUpdate":{"__t":"c","agent":{"deviceProperties":{"manufacturer":"contoso","model":"vacuum-v1","interfaceId":null,"contractModelId":"dtmi:azure:iot:deviceUpdateContractModel;2","aduVer":"DU;agent\/1.0.0"},"compatPropertyNames":"manufacturer,model"}}} [ADUC_D2C_Default_Message_Transport_Function] +2022-11-30T15:19:18.7250Z 33853[33853] [D] Sending D2C message (t:2, retries:0). [ProcessMessage] +2022-11-30T15:19:18.7250Z 33853[33853] [D] Sending D2C message: +{"deviceInformation":{"__t":"c","manufacturer":"contoso","model":"vacuum-v1","osName":"Ubuntu Core","swVersion":"20","processorArchitecture":"x86_64","processorManufacturer":"GenuineIntel","totalMemory":5776704,"totalStorage":64768}} [ADUC_D2C_Default_Message_Transport_Function] + +... + +2022-11-30T15:19:20.5271Z 33853[33853] [D] Send message completed (status:3) [OnDeviceInfoD2CMessageCompleted] + +... + +``` + +Verify that the Device Twin contains properties reported by the agent: + +```json +{ + "deviceId": "...", + + ... + + "modelId": "dtmi:azure:iot:deviceUpdateModel;2", + "version": 10, + "tags": { + "ADUGroup": "ucore-test-1" + }, + "properties": { + "desired": { + ... + }, + "reported": { + "deviceUpdate": { + "__t": "c", + "agent": { + "deviceProperties": { + "manufacturer": "contoso", + "model": "vacuum-v1", + "contractModelId": "dtmi:azure:iot:deviceUpdateContractModel;2", + "aduVer": "DU;agent/1.0.0" + }, + "compatPropertyNames": "manufacturer,model" + } + }, + "deviceInformation": { + "__t": "c", + "manufacturer": "contoso", + "model": "vacuum-v1", + "osName": "Ubuntu Core", + "swVersion": "20", + "processorArchitecture": "x86_64", + "processorManufacturer": "GenuineIntel", + "totalMemory": 5776704, + "totalStorage": 64768 + }, + + ... + + "$version": 8 + } + }, + "capabilities": { + "iotEdge": true + }, + "deviceScope": "ms-azure-iot-edge://ucore-test-1-638054160455707418" +} +``` + ## View Snap Logs using Journalctl The name of the Device Update agent in the confined environment is `snap.deviceupdate-agent.deviceupdate-agent`. To view the journal log, use following command: -```sh -journalctl -u snap.deviceupdate-agent.deviceupdate-agent -f --no-tail +```shell +$ journalctl -u snap.deviceupdate-agent.deviceupdate-agent -f --no-tail +``` + +To start the daemon: + +```shell +$ sudo systemctl start snap.deviceupdate-agent.deviceupdate-agent ``` +To stop the daemon: -## Configure The Snap -[TBD] +```shell +$ sudo systemctl stop snap.deviceupdate-agent.deviceupdate-agent +``` -## Testing The Snap -[TBD] +To restart the daemon: + +```shell +$ sudo systemctl restart snap.deviceupdate-agent.deviceupdate-agent +``` diff --git a/snap/hooks/post-refresh b/snap/hooks/configure similarity index 80% rename from snap/hooks/post-refresh rename to snap/hooks/configure index 284a533b5..320f91792 100644 --- a/snap/hooks/post-refresh +++ b/snap/hooks/configure @@ -11,18 +11,18 @@ adu_group=snap_aziot_du # Use getent and cut to get the info for the adu user and parse out the home directory. adu_home_dir=$(getent passwd $adu_user | cut -d: -f6) -adu_conf_dir=/etc/adu +adu_conf_dir=$SNAP_DATA/config adu_conf_file=du-config.json adu_diagnostics_conf_file=du-diagnostics-config.json -adu_log_dir=/var/log/adu -adu_data_dir=/var/lib/adu +adu_log_dir=$SNAP_DATA/log +adu_data_dir=$SNAP_DATA/data adu_downloads_dir="$adu_data_dir/downloads" -adu_shell_dir=/usr/lib/adu +adu_shell_dir=$SNAP/usr/lib/adu adu_shell_file=adu-shell adu_bin_path=$SNAP/usr/bin/AducIotAgent adu_extensions_dir="$adu_data_dir/extensions" -adu_extensions_sources_dir="$adu_extensions_dir/sources" +adu_extensions_sources_dir="$SNAP/var/lib/adu/extensions/sources" adu_script_handler_file=libmicrosoft_script_1.so adu_steps_handler_file=libmicrosoft_steps_1.so @@ -34,8 +34,8 @@ sample_du_config=$( { "schemaVersion": "1.1", "aduShellTrustedUsers": [ - "adu", - "do" + "snap_aziot_du", + "snap_aziot_do" ], "iotHubProtocol": "mqtt", "manufacturer": , @@ -43,7 +43,7 @@ sample_du_config=$( "agents": [ { "name": , - "runas": "adu", + "runas": "snap_aziot_du", "connectionSource": { "connectionType": "string", "connectionData": @@ -64,10 +64,6 @@ sample_du_diagnostics_config=$( { "componentName":"adu", "logPath":"/var/log/adu/" - }, - { - "componentName":"do", - "logPath":"/var/log/deliveryoptimization-agent/" } ], "maxKilobytesToUploadPerLogPath":50 @@ -134,91 +130,94 @@ setup_dirs_and_files() { mkdir -p "$adu_conf_dir" # Ensure the right owners and permissions - chown "$adu_user:$adu_group" "$adu_conf_dir" - chmod u=rwx,g=rx,o= "$adu_conf_dir" + #chown "$adu_user:$adu_group" "$adu_conf_dir" + #chmod u=rwx,g=rx,o= "$adu_conf_dir" # Generate the template configuration file echo "Generate the template configuration file..." if [ ! -f "$adu_conf_dir/${adu_conf_file}.template" ]; then echo "$sample_du_config" > "$adu_conf_dir/${adu_conf_file}.template" - chown "$adu_user:$adu_group" "$adu_conf_dir/${adu_conf_file}.template" - chmod u=r,g=r,o= "$adu_conf_dir/${adu_conf_file}.template" + #chown "$adu_user:$adu_group" "$adu_conf_dir/${adu_conf_file}.template" + #chmod u=r,g=r,o= "$adu_conf_dir/${adu_conf_file}.template" fi # Create configuration file from template if [ ! -f "$adu_conf_dir/$adu_conf_file" ]; then cp -a "$adu_conf_dir/${adu_conf_file}.template" "$adu_conf_dir/$adu_conf_file" - chmod u=rw,g=r,o= "$adu_conf_dir/$adu_conf_file" + #chmod u=rw,g=r,o= "$adu_conf_dir/$adu_conf_file" fi echo "Generating the diagnostics configuration file..." if [ ! -f "$adu_conf_dir/$adu_diagnostics_conf_file" ]; then echo "$sample_du_diagnostics_config" > "$adu_conf_dir/$adu_diagnostics_conf_file" - chown "$adu_user:$adu_group" "$adu_conf_dir/$adu_diagnostics_conf_file" - chmod u=r,g=r,o= "$adu_conf_dir/$adu_diagnostics_conf_file" + #chown "$adu_user:$adu_group" "$adu_conf_dir/$adu_diagnostics_conf_file" + #chmod u=r,g=r,o= "$adu_conf_dir/$adu_diagnostics_conf_file" fi # Create home dir if [ ! -d "$adu_home_dir" ]; then mkdir -p "$adu_home_dir" - chown "$adu_user:$adu_group" "$adu_home_dir" - chmod u=rwx,g=rx "$adu_home_dir" + #chown "$adu_user:$adu_group" "$adu_home_dir" + #chmod u=rwx,g=rx "$adu_home_dir" fi # Create log dir if [ ! -d "$adu_log_dir" ]; then + echo "Create log dir ($adu_log_dir)..." mkdir -p "$adu_log_dir" fi # Ensure the right owners and permissions - chown "$adu_user:$adu_group" "$adu_log_dir" - chmod u=rwx,g=rx "$adu_log_dir" + #chown "$adu_user:$adu_group" "$adu_log_dir" + #chmod u=rwx,g=rx "$adu_log_dir" # Create data dir - echo "Create data dir..." if [ ! -d "$adu_data_dir" ]; then + echo "Create data dir ($adu_data_dir)..." mkdir -p "$adu_data_dir" fi # Ensure the right owners and permissions - chown "$adu_user:$adu_group" "$adu_data_dir" - chmod u=rwx,g=rwx,o= "$adu_data_dir" + #chown "$adu_user:$adu_group" "$adu_data_dir" + #chmod u=rwx,g=rwx,o= "$adu_data_dir" ls -ld "$adu_data_dir" # Create downloads dir if [ ! -d "$adu_downloads_dir" ]; then + echo "Create downloads dir ($adu_downloads_dir)..." mkdir -p "$adu_downloads_dir" fi # Ensure the right owners and permissions - chown "$adu_user:$adu_group" "$adu_downloads_dir" - chmod u=rwx,g=rwx,o= "$adu_downloads_dir" + #chown "$adu_user:$adu_group" "$adu_downloads_dir" + #chmod u=rwx,g=rwx,o= "$adu_downloads_dir" ls -ld "$adu_downloads_dir" # Create extensions dir if [ ! -d "$adu_extensions_dir" ]; then + echo "Create extensions dir ($adu_extensions_dir)..." mkdir -p "$adu_extensions_dir" fi # Ensure the right owners and permissions - chown "$adu_user:$adu_group" "$adu_extensions_dir" - chmod u=rwx,g=rwx,o= "$adu_extensions_dir" + #chown "$adu_user:$adu_group" "$adu_extensions_dir" + #chmod u=rwx,g=rwx,o= "$adu_extensions_dir" # Create extensions sources dir - if [ ! -d "$adu_extensions_sources_dir" ]; then - mkdir -p "$adu_extensions_sources_dir" - fi + # if [ ! -d "$adu_extensions_sources_dir" ]; then + # mkdir -p "$adu_extensions_sources_dir" + # fi # Ensure the right owners and permissions - chown "$adu_user:$adu_group" "$adu_extensions_sources_dir" - chmod u=rwx,g=rwx,o= "$adu_extensions_sources_dir" + #chown "$adu_user:$adu_group" "$adu_extensions_sources_dir" + #chmod u=rwx,g=rwx,o= "$adu_extensions_sources_dir" #Set deviceupdate-agent owner and permission if [ ! -f "$adu_shell_dir/$adu_shell_file" ]; then echo "ERROR! $adu_shell_dir/$adu_shell_file does not exists." >&2 - else - chown "root:$adu_group" "$adu_shell_dir/$adu_shell_file" - chmod u=rxs "$adu_shell_dir/$adu_shell_file" + #else + # chown "root:$adu_group" "$adu_shell_dir/$adu_shell_file" + # chmod u=rxs "$adu_shell_dir/$adu_shell_file" fi else echo "ERROR! $adu_user does not exist." >&2 @@ -239,6 +238,6 @@ register_extensions() { $adu_bin_path -l 2 --extension-type contentDownloader --register-extension $adu_extensions_sources_dir/$adu_curl_downloader_file } -add_adu_user_and_group +#add_adu_user_and_group setup_dirs_and_files register_extensions diff --git a/snap/hooks/install b/snap/hooks/install deleted file mode 100644 index 975371f79..000000000 --- a/snap/hooks/install +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/sh - -# adu_user is the user that the ADU Agent daemon will run as. -# ADU Agent daemon needs to run as 'adu' to be able to perform high-privilege tasks via adu-shell. -adu_user=snap_aziot_du - -# The adu_group is the group that gives partner users like DO user -# access to ADU resources like download sandbox folder. -adu_group=snap_aziot_du - -# -# exit codes -# -exitcode_success=0 -exitcode_adu_group_already_exists=1 -exitcode_adu_user_already_exists=2 -exitcode_pre_ppr_installed=3 # pre-ppr agent binary is installed -exitcode_pre_ppr_unpurged=4 # pre-ppr config exists -exitcode_unknown_argument=5 - -add_adu_user_and_group() { - echo "Create the 'adu' group." - if ! getent group "$adu_group" > /dev/null; then - addgroup --system "$adu_group" - else - # For security purposes, it's extremely important that there's no existing 'adu' user and 'adu' group. - # This ensures the account and group are setup by our package installation process only, - # and no other actor can pre-create the user/group with nefarious intentions. - echo "Error: 'adu' group already exists." >&2 - exit $exitcode_adu_group_already_exists - fi - - echo "Create the 'adu' user." # With no shell and no login, in 'adu' group. - if ! getent passwd "$adu_user" > /dev/null; then - adduser --system "$adu_user" --ingroup "$adu_group" --no-create-home --shell /bin/false - else - # For security purposes, it's extremely important that there's no existing 'adu' user and 'adu' group. - # This to ensures the account and group are setup by our package installation process only, - # and no other actor can pre-create the user/group with nefarious intentions. - echo "Error: 'adu' user already exists." >&2 - exit $exitcode_adu_user_already_exists - fi - - echo "Add the 'adu' user to the 'syslog' group." # To allow ADU to write to /var/log folder - if getent group "syslog" > /dev/null; then - usermod -aG "syslog" "$adu_user" - fi -} - -add_adu_user_and_group diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 290e79fae..2c449b69d 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -17,6 +17,9 @@ confinement: devmode # use 'strict' once you have the right plugs and slots # https://snapcraft.io/docs/parts-lifecycle # plugin - Specifies a plugin required for building the part. # +# see also: +# environment variables - https://snapcraft.io/docs/parts-environment-variables +# ##### parts: @@ -25,9 +28,10 @@ parts: source: . override-build: | ./scripts/install-deps.sh -a - mkdir -p ../install/usr/lib - cp /usr/local/lib/libdeliveryoptimization.so* ../install/usr/lib - + mkdir -p $SNAPCRAFT_PART_INSTALL/usr/lib + cp /usr/local/lib/libdeliveryoptimization.so* $SNAPCRAFT_PART_INSTALL/usr/lib + stage-packages: + - libboost-filesystem1.71.0 # # Agent component # This is the main agent application. @@ -37,17 +41,13 @@ parts: source: . override-build: | ./scripts/build.sh --ubuntu-core-snap-only - mkdir -p ../install/usr/bin - mkdir -p ../install/usr/lib/adu - mkdir -p ../install/var/lib/adu/extensions/sources - mkdir -p ../install/conf - echo 'placeholder' > ../install/conf/du-config.json - echo 'placeholder' > ../install/conf/du-diagnostic.json - cp ../build/out/bin/AducIotAgent ../install/usr/bin/AducIotAgent - cp ../build/out/bin/adu-shell ../install/usr/lib/adu - cp ../build/out/lib/*.so ../install/var/lib/adu/extensions/sources - - # cp /usr/lib/libdeliveryoptimization.so* ../install/usr/lib + mkdir -p $SNAPCRAFT_PART_INSTALL/usr/bin + mkdir -p $SNAPCRAFT_PART_INSTALL/usr/lib/adu + mkdir -p $SNAPCRAFT_PART_INSTALL/var/lib/adu/extensions/sources + mkdir -p $SNAPCRAFT_PART_INSTALL/conf + cp $SNAPCRAFT_PART_BUILD/out/bin/AducIotAgent $SNAPCRAFT_PART_INSTALL/usr/bin/AducIotAgent + cp $SNAPCRAFT_PART_BUILD/out/bin/adu-shell $SNAPCRAFT_PART_INSTALL/usr/lib/adu + cp $SNAPCRAFT_PART_BUILD/out/lib/*.so $SNAPCRAFT_PART_INSTALL/var/lib/adu/extensions/sources # Require install-deps after: @@ -59,6 +59,7 @@ parts: - var/lib/adu/extensions/sources/*.so - conf/*.* stage-packages: + - curl - libasn1-8-heimdal - libboost-filesystem1.71.0 - libbrotli1 @@ -81,21 +82,23 @@ parts: - libxml2 apps: - # high-previlege tasks broker + # high-privilege tasks broker adu-shell: command: usr/lib/adu/adu-shell plugs: + - home - network # 'deviceupdate-agent' service # See document: https://forum.snapcraft.io/t/snapcraft-app-and-service-metadata/8335 deviceupdate-agent: - command: usr/bin/AducIotAgent + command: usr/bin/AducIotAgent -l 0 -e daemon: simple refresh-mode: restart - restart-condition: always + restart-condition: on-failure restart-delay: 10s plugs: + - home - network environment: @@ -104,10 +107,16 @@ environment: layout: # adu_data_dir /var/lib/adu: - symlink: $SNAP/var/lib/adu + symlink: $SNAP_DATA/data # adu_conf_dir /etc/adu: - symlink: $SNAP/config + symlink: $SNAP_DATA/config # adu_shell_dir /usr/lib/adu: symlink: $SNAP/usr/lib/adu +# adu_log_dir + /var/log/adu: + symlink: $SNAP_DATA/log +# curl command + /usr/bin/curl-downloader: + symlink: $SNAP/usr/bin/curl diff --git a/src/agent/src/main.c b/src/agent/src/main.c index ea656fc34..c7291c3f2 100644 --- a/src/agent/src/main.c +++ b/src/agent/src/main.c @@ -1252,6 +1252,7 @@ _Bool StartupAgent(const ADUC_LaunchArguments* launchArgs) result = ExtensionManager_InitializeContentDownloader(NULL /*initializeData*/); } +#ifndef ADUC_UBUNTU_CORE_SNAP_ONLY if (InitializeCommandListenerThread()) { RegisterCommand(&redoUpdateCommand); @@ -1263,6 +1264,7 @@ _Bool StartupAgent(const ADUC_LaunchArguments* launchArgs) // Note: even though we can't create command listener here, we need to ensure that // the agent stay alive and connected to the IoT hub. } +#endif if (IsAducResultCodeFailure(result.ResultCode)) { @@ -1287,7 +1289,9 @@ void ShutdownAgent() { Log_Info("Agent is shutting down with signal %d.", g_shutdownSignal); ADUC_D2C_Messaging_Uninit(); +#ifndef ADUC_UBUNTU_CORE_SNAP_ONLY UninitializeCommandListenerThread(); +#endif ADUC_PnP_Components_Destroy(); ADUC_DeviceClient_Destroy(g_iotHubClientHandle); DiagnosticsComponent_DestroyDeviceName(); diff --git a/src/extensions/content_downloaders/curl_downloader/CMakeLists.txt b/src/extensions/content_downloaders/curl_downloader/CMakeLists.txt index a7f0eaa18..c0859c542 100644 --- a/src/extensions/content_downloaders/curl_downloader/CMakeLists.txt +++ b/src/extensions/content_downloaders/curl_downloader/CMakeLists.txt @@ -13,6 +13,10 @@ target_sources ( target_include_directories (${PROJECT_NAME} PUBLIC ${ADU_EXTENSION_INCLUDES} ${ADU_EXPORT_INCLUDES}) +target_compile_definitions ( + ${PROJECT_NAME} + PRIVATE ADUC_UBUNTU_CORE_SNAP_ONLY="${ADUC_UBUNTU_CORE_SNAP_ONLY}") + target_link_libraries ( ${PROJECT_NAME} PRIVATE aduc::contract_utils diff --git a/src/extensions/content_downloaders/curl_downloader/curl_content_downloader.cpp b/src/extensions/content_downloaders/curl_downloader/curl_content_downloader.cpp index 7e754021f..798ac2e8e 100644 --- a/src/extensions/content_downloaders/curl_downloader/curl_content_downloader.cpp +++ b/src/extensions/content_downloaders/curl_downloader/curl_content_downloader.cpp @@ -16,6 +16,12 @@ #include // for stat #include +#ifdef ADUC_UBUNTU_CORE_SNAP_ONLY +# define CURL_PATH "/usr/bin/curl-downloader" +#else +# define CURL_PATH "/usr/bin/curl" +#endif + ADUC_Result Download_curl( const ADUC_FileEntity* entity, const char* workflowId, @@ -110,7 +116,7 @@ ADUC_Result Download_curl( args.emplace_back("-O"); args.emplace_back(entity->DownloadUri); - exitCode = ADUC_LaunchChildProcess("/usr/bin/curl", args, output); + exitCode = ADUC_LaunchChildProcess(CURL_PATH, args, output); if (exitCode == 0) { @@ -124,6 +130,16 @@ ADUC_Result Download_curl( goto done; } + if (IsAducResultCodeFailure(result.ResultCode)) + { + Log_Error( + "Download job end. exit code: %d, resultCode: %d, extendedCode: %d (0x%X)", + exitCode, + result.ResultCode, + result.ExtendedResultCode, + result.ExtendedResultCode); + } + Log_Info("Download output:: \n%s", output.c_str()); // If we downloaded successfully, validate the file hash. @@ -175,10 +191,13 @@ ADUC_Result Download_curl( } } - Log_Info( - "Download task end. resultCode: %d, extendedCode: %d (0x%X)", - result.ResultCode, - result.ExtendedResultCode, - result.ExtendedResultCode); + if (IsAducResultCodeSuccess(result.ResultCode)) + { + Log_Info( + "Download job end. resultCode: %d, extendedCode: %d (0x%X)\n", + result.ResultCode, + result.ExtendedResultCode, + result.ExtendedResultCode); + } return result; } diff --git a/src/extensions/step_handlers/script_handler/src/script_handler.cpp b/src/extensions/step_handlers/script_handler/src/script_handler.cpp index 8322c77ac..fd137fc0d 100644 --- a/src/extensions/step_handlers/script_handler/src/script_handler.cpp +++ b/src/extensions/step_handlers/script_handler/src/script_handler.cpp @@ -5,10 +5,10 @@ * @copyright Copyright (c) Microsoft Corporation. * Licensed under the MIT License. */ -#include "aduc/script_handler.hpp" #include "aduc/extension_manager.hpp" #include "aduc/logging.h" #include "aduc/process_utils.hpp" // ADUC_LaunchChildProcess +#include "aduc/script_handler.hpp" #include "aduc/string_c_utils.h" // IsNullOrEmpty #include "aduc/string_utils.hpp" // ADUC::StringUtils::Split #include "aduc/system_utils.h" // ADUC_SystemUtils_MkSandboxDirRecursive @@ -18,6 +18,7 @@ #include "adushell_const.hpp" #include #include +#include #include #define HANDLER_PROPERTIES_SCRIPT_FILENAME "scriptFileName" @@ -155,6 +156,11 @@ static ADUC_Result Script_Handler_DownloadPrimaryScriptFile(ADUC_WorkflowHandle result.ExtendedResultCode = ADUC_ERC_SCRIPT_HANDLER_DOWNLOAD_PRIMARY_FILE_FAILURE_UNKNOWNEXCEPTION; } + if (IsAducResultCodeFailure(result.ResultCode)) + { + Log_Error("Cannot download the primary script file, error %d", result.ExtendedResultCode); + } + workflow_free_file_entity(entity); entity = nullptr; @@ -515,7 +521,8 @@ ADUC_Result ScriptHandlerImpl::Install(const tagADUC_WorkflowData* workflowData) return result; } -static ADUC_Result ScriptHandler_PerformAction(const std::string& action, const tagADUC_WorkflowData* workflowData) +static ADUC_Result +ScriptHandler_PerformAction(const std::string& action, const tagADUC_WorkflowData* workflowData, bool useAduShell) { Log_Info("Action (%s) begin", action.c_str()); ADUC_Result result = { ADUC_GeneralResult_Failure }; @@ -538,10 +545,15 @@ static ADUC_Result ScriptHandler_PerformAction(const std::string& action, const JSON_Value* actionResultValue = nullptr; JSON_Object* actionResultObject = nullptr; - std::vector aduShellArgs = { adushconst::update_type_opt, - adushconst::update_type_microsoft_script, - adushconst::update_action_opt, - adushconst::update_action_execute }; + std::vector aduShellArgs; + + if (useAduShell) + { + aduShellArgs.emplace_back(adushconst::update_type_opt); + aduShellArgs.emplace_back(adushconst::update_type_microsoft_script); + aduShellArgs.emplace_back(adushconst::update_action_opt); + aduShellArgs.emplace_back(adushconst::update_action_execute); + } result = ScriptHandlerImpl::PrepareScriptArguments( workflowData->WorkflowHandle, scriptResultFile, scriptWorkfolder, scriptFilePath, args); @@ -560,15 +572,24 @@ static ADUC_Result ScriptHandler_PerformAction(const std::string& action, const goto done; } - aduShellArgs.emplace_back(adushconst::target_data_opt); + if (useAduShell) + { + aduShellArgs.emplace_back(adushconst::target_data_opt); + } aduShellArgs.emplace_back(scriptFilePath); - aduShellArgs.emplace_back(adushconst::target_options_opt); + if (useAduShell) + { + aduShellArgs.emplace_back(adushconst::target_options_opt); + } aduShellArgs.emplace_back(action.c_str()); for (auto a : args) { - aduShellArgs.emplace_back(adushconst::target_options_opt); + if (useAduShell) + { + aduShellArgs.emplace_back(adushconst::target_options_opt); + } aduShellArgs.emplace_back(a); } @@ -579,10 +600,40 @@ static ADUC_Result ScriptHandler_PerformAction(const std::string& action, const { ss << " " << a; } - Log_Debug("##########\n# ADU-SHELL ARGS:\n##########\n %s", ss.str().c_str()); + Log_Debug("##########\n# COMMAND RUNNER ARGS:\n##########\n %s", ss.str().c_str()); + } + + if (!useAduShell) + { + // Ensure that the script has the correct file permission. + const char* path = scriptFilePath.c_str(); + struct stat st = {}; + bool filePermissionsChanged = false; + bool statOk = stat(path, &st) == 0; + int mode = S_IRWXU | S_IRGRP | S_IXGRP; + if (statOk) + { + int perms = st.st_mode & ~S_IFMT; + if (perms != mode) + { + // Fix the permissions. + if (0 != chmod(path, mode)) + { + filePermissionsChanged = true; + stat(path, &st); + Log_Warn( + "Failed to set '%s' file permissions (expected:%d, actual: %d)", + path, + mode, + st.st_mode & ~S_IFMT); + } + } + } } - exitCode = ADUC_LaunchChildProcess(adushconst::adu_shell, aduShellArgs, scriptOutput); + // Execute the script using adu-shell or directly. + exitCode = ADUC_LaunchChildProcess( + useAduShell ? adushconst::adu_shell : scriptFilePath.c_str(), aduShellArgs, scriptOutput); if (!scriptOutput.empty()) { @@ -666,12 +717,12 @@ static ADUC_Result ScriptHandler_PerformAction(const std::string& action, const ADUC_Result ScriptHandlerImpl::PerformAction(const std::string& action, const tagADUC_WorkflowData* workflowData) { - return ScriptHandler_PerformAction(action, workflowData); + return ScriptHandler_PerformAction(action, workflowData, true); } static ADUC_Result DoCancel(const tagADUC_WorkflowData* workflowData) { - return ScriptHandler_PerformAction("--action-cancel", workflowData); + return ScriptHandler_PerformAction("--action-cancel", workflowData, true); } /** diff --git a/src/utils/extension_utils/CMakeLists.txt b/src/utils/extension_utils/CMakeLists.txt index cc2825c4c..a377a0a26 100644 --- a/src/utils/extension_utils/CMakeLists.txt +++ b/src/utils/extension_utils/CMakeLists.txt @@ -28,6 +28,7 @@ target_compile_definitions ( ADUC_CONTENT_DOWNLOADER_EXTENSION_DIR="${ADUC_CONTENT_DOWNLOADER_EXTENSION_DIR}" ADUC_DOWNLOAD_HANDLER_EXTENSION_DIR="${ADUC_DOWNLOAD_HANDLER_EXTENSION_DIR}" ADUC_DOWNLOAD_HANDLER_REG_FILENAME="${ADUC_DOWNLOAD_HANDLER_REG_FILENAME}" + ADUC_UBUNTU_CORE_SNAP_ONLY="${ADUC_UBUNTU_CORE_SNAP_ONLY}" ADUC_FILE_GROUP="${ADUC_FILE_GROUP}" ADUC_FILE_USER="${ADUC_FILE_USER}") diff --git a/src/utils/extension_utils/src/extension_utils.c b/src/utils/extension_utils/src/extension_utils.c index 4e4b17e2e..4109d390d 100644 --- a/src/utils/extension_utils/src/extension_utils.c +++ b/src/utils/extension_utils/src/extension_utils.c @@ -232,10 +232,15 @@ static _Bool RegisterHandlerExtension( grp = NULL; Log_Debug("Creating the extension folder ('%s'), uid:%d, gid:%d", STRING_c_str(dir), aduUserId, aduGroupId); +#ifndef ADUC_UBUNTU_CORE_SNAP_ONLY int dir_result = ADUC_SystemUtils_MkDirRecursive(STRING_c_str(dir), aduUserId, aduGroupId, S_IRWXU | S_IRWXG); +#else + // Note: for Ubuntu Core, always use default user and group. + int dir_result = ADUC_SystemUtils_MkDirRecursive(STRING_c_str(dir), -1, -1, S_IRWXU | S_IRWXG); +#endif if (dir_result != 0) { - Log_Error("Cannot create a folder for registration file. ('%s')", STRING_c_str(dir)); + Log_Error("Cannot create a folder for handler registration file. ('%s')", STRING_c_str(dir)); goto done; } @@ -413,8 +418,14 @@ _Bool RegisterExtension(const char* extensionDir, const char* extensionFilePath) grp = NULL; Log_Debug("Creating the extension folder ('%s'), uid:%d, gid:%d", extensionDir, aduUserId, aduGroupId); +#ifndef ADUC_UBUNTU_CORE_SNAP_ONLY int dir_result = ADUC_SystemUtils_MkDirRecursive(extensionDir, aduUserId, aduGroupId, S_IRWXU | S_IRGRP | S_IWGRP | S_IXGRP); +#else + // Note: for Ubuntu Core, always use default user and group. + int dir_result = + ADUC_SystemUtils_MkDirRecursive(extensionDir, -1, -1, S_IRWXU | S_IRGRP | S_IWGRP | S_IXGRP); +#endif if (dir_result != 0) { Log_Error("Cannot create a folder for registration file. ('%s')", extensionDir);