Initial commit of new enhanced version of update-service.

This commit is contained in:
Tonoxis
2023-02-05 07:07:20 -05:00
commit 266f9910b7
12 changed files with 909 additions and 0 deletions

9
.gitignore vendored Executable file
View File

@@ -0,0 +1,9 @@
vendor/
composer.lock
BUILD-DATA
images/
config.prop
tononixOS.pgp
*.gpg
*.asc
.gpg/

29
composer.json Executable file
View File

@@ -0,0 +1,29 @@
{
"name": "tononixos/update-service",
"description": "tononixOS Update Services",
"type": "project",
"authors": [
{
"name": "Richard Blair",
"email": "dreamcaster23@gmail.com"
}
],
"require": {
"slim/slim": "^4.11",
"slim/psr7": "^1.6",
"phar-io/gnupg": "^1.0",
"psr/cache": "^1.0",
"psr/container": "1.1",
"psr/event-dispatcher": "^1.0",
"psr/simple-cache": "1.0.1",
"react/react": "^1.3",
"hassankhan/config": "^3.1",
"php-di/php-di": "^7.0",
"slim/twig-view": "^3.3"
},
"config": {
"platform": {
"php": "8.0"
}
}
}

42
libSignedComms.php Executable file
View File

@@ -0,0 +1,42 @@
<?php
class SignedCommunicationProvider
{
protected $GPG;
public function __construct($keyFile, $fingerprint = null, $passphrase = null, $tmpPath = null)
{
if(empty($tmpPath)) {
$tmpPath = "/tmp/tononixOS.gpg";
}
if (file_exists($tmpPath)) {
unlink($tmpPath);
}
$this->GPG = new GnuPG(['home-dir' => $tmpPath]);
$keyData = file_get_contents($keyFile);
$this->GPG->import($keyData);
if (!empty($fingerprint)) {
$result = $this->GPG->addsignkey($fingerprint, $passphrase);
if ($result == false) {
return false;
} else {
$this->GPG->setsignmode(GNUPG_SIG_MODE_NORMAL);
return true;
}
}
}
public function sign($data)
{
if ($this->GPG == null) {
return false;
}
$data = $this->GPG->sign($data);
return $data;
}
public function verify($data)
{
// Verification of signed data should be here.
}
}

147
libUpdateService.php Executable file
View File

@@ -0,0 +1,147 @@
<?php
use Noodlehaus\Config;
use Noodlehaus\Parser\Xml;
use System\Diagnostics;
class RecoveryUpdateService {
function getBootloaderUpdateVersion()
{
$recoveryManifest = new Config(__DIR__."/manifests/recovery.xml", new Xml);
if(defined("PEACHPIE_VERSION"))
{
$recoveryManifest = $recoveryManifest->all();
\System\Diagnostics\Debug::WriteLine("Recovery Version: ".$recoveryManifest['version'], "RecoveryUpdateService");
}
return $recoveryManifest['version'];
}
function getBootloaderUpdateManifest()
{
$recoveryManifest = new Config(__DIR__."/manifests/recovery.xml", new Xml);
// Tweak to configuration to allow PeachPie to compile it as .NET
// We do this because if we do it the normal way using $conf->get() it fails to display anything.
$recoveryManifest = $recoveryManifest->all();
$buildDate = $recoveryManifest['build-date'];
$version = $recoveryManifest['version'];
$pinn_commit = $recoveryManifest['git']['pinn']['commit'];
$rpi_userland_branch = $recoveryManifest['git']['rpi-userland']['branch'];
$rpi_userland_commit = $recoveryManifest['git']['rpi-userland']['commit'];
$rpi_firmware_branch = $recoveryManifest['git']['rpi-firmware']['branch'];
$rpi_firmware_commit = $recoveryManifest['git']['rpi-firmware']['commit'];
$kernel_branch = $recoveryManifest['git']['rpi-linux']['branch'];
$kernel_commit = $recoveryManifest['git']['rpi-linux']['commit'];
$buildData = <<<EOF
Build-date: $buildDate
PINN Version: $version
PINN Git HEAD @ $pinn_commit
rpi-userland Git $rpi_userland_branch @ $rpi_userland_commit
rpi-firmware Git $rpi_firmware_branch @ $rpi_firmware_commit
rpi-linux Git $kernel_branch @ $kernel_commit
EOF;
return $buildData;
}
function getBootloaderUpdateOTA()
{
$recoveryManifest = new Config(__DIR__."/manifests/recovery.xml", new Xml);
// Tweak to allow PeachPie compiled version to read the manifest.
$recoveryManifest = $recoveryManifest->all();
$recoveryFile = "images/recovery/".$recoveryManifest['version'].".zip";
return $recoveryFile;
}
function getRecoveryInstallerRepository()
{
$repositoryConfig = new Config(__DIR__.'/repository.xml', new Xml);
$repositoryArray = array("os_list"=>[]);
$i = 0;
foreach($repositoryConfig['repository'] as $repository)
{
$repositoryArray['os_list'][$i] = [
'description' => $repository['@attributes']['description'],
'download_size' => $repository['download-size'],
'group' => $repository['@attributes']['group'],
'icon' => $repository['icon'],
'marketing_info' => $repository['url']['marketing-info'],
'nominal_size' => $repository['nominal-size'],
'os_info' => $repository['url']['os-info'],
'os_name' => $repository['@attributes']['name'],
'partition_setup' => $repository['url']['partition-setup'],
'partition_info' => $repository['url']['partition-info'],
'release_date' => $repository['release-date'],
'supported_models' => $repository['supported-model'],
'supports_backup' => $repository['supports-backup'],
'tarballs' => $repository['url']['tarball'],
'version' => $repository['version']
];
}
return json_encode($repositoryArray, JSON_PRETTY_PRINT+JSON_UNESCAPED_SLASHES);
}
function getRecoveryInstallerRepositoryList()
{
// Generate repo_list.json file.
}
}
class OSUpdateService {
function getLatestVersionInformation($operatingSystem)
{
$osManifest = new Config("manifests/os/".$operatingSystem.".xml", new Xml);
if(defined("PEACHPIE_VERSION"))
{
$osManifest = $osManifest->all();
\System\Diagnostics\Debug::write(json_encode($osManifest)."\r\n","OSUpdateService");
}
$osResponse = array(
"os-version" =>$osManifest['info']['version'],
"release-date" => $osManifest['info']['release-date']);
return $osResponse;
}
function getAvailableOSList()
{
$osmanifestlist = array();
$i = 0;
$flags = \FilesystemIterator::SKIP_DOTS;// some flags to filter . and .. and follow symlinks
$iterator = new \RecursiveDirectoryIterator(dirname('manifests/os/'), $flags);// create a simple recursive directory iterator
$iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST);// make it a truly recursive iterator
foreach($iterator as $fileinfo) {
if($fileinfo->getExtension() == "xml") {
if($fileinfo->getFilename() != "recovery") {
$osmanifest = new Config($fileinfo->__toString(), new Xml);
if(defined("PEACHPIE_VERSION")) {
$osmanifest = $osmanifest->all();
}
if(empty($osmanifest['info']['@attributes']['name'])) {
break;
}
$osmanifestlist[$i]['name'] = $osmanifest['info']['@attributes']['name'];
if(defined("PEACHPIE_VERSION")) \System\Diagnostics\Debug::writeLine($osmanifestlist[$i]['name']);
$osmanifestlist[$i]['version'] = $osmanifest['info']['version'];
$osmanifestlist[$i]['release-date'] = $osmanifest['info']['release-date'];
$i++;
}
}
}
return $osmanifestlist;
}
}

61
manifests/os/tononixOS.xml Executable file
View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<distribution>
<!-- Attributes provide the name, description, icon and the group that PINN will put the OS in. -->
<info name="tononixOS Home Edition" description="tononixOS Home Edition" icon="" group="General">
<homepage></homepage>
<release-date>TBA</release-date>
<version>1.0.0</version>
<!-- Supported models should be referred to by name, PINN uses these. -->
<supported-model>Raspberry Pi 3 Model B+</supported-model>
<supported-model>Raspberry Pi 3</supported-model>
<supported-model>Raspberry Pi 4</supported-model>
</info>
<supports-backup>false</supports-backup>
<nominal-size></nominal-size>
<download-size></download-size>
<!-- Attributes control whether the tarball signature check must pass and/or if the checksum check must pass -->
<security verifyTarballSignature="true" enforceChecksum="true">
<md5sum></md5sum>
<sha1sum></sha1sum>
<sha256sum></sha256sum>
<sha512sum></sha512sum>
</security>
<!-- Settings for the OS -->
<settings>
<!-- Default user account -->
<defaults>
<username>user</username>
<password>user</password>
</defaults>
<!-- Boot options -->
<boot>
<supports-usb-boot>false</supports-usb-boot>
<supports-sda-boot>false</supports-sda-boot>
<supports-sda-root>false</supports-sda-root>
</boot>
<!-- Partition table layout -->
<partitions>
<partition label="boot" type="FAT">
<mkfs-options>-F 32</mkfs-options>
<uncompressed-tarball-size></uncompressed-tarball-size>
<partition-size></partition-size>
<want-maximised>false</want-maximised>
<tarball></tarball>
</partition>
<partition label="root" type="ext4">
<mkfs-options>-c</mkfs-options>
<want-maximised>true</want-maximised>
<uncompressed-tarball-size></uncompressed-tarball-size>
<partition-size></partition-size>
<tarball></tarball>
</partition>
<partition label="appfs" type="ext4" version="1.0">
<mkfs-options>-c</mkfs-options>
<want-maximised>true</want-maximised>
<uncompressed-tarball-size></uncompressed-tarball-size>
<partition-size></partition-size>
<tarball></tarball>
</partition>
</partitions>
</settings>
</distribution>

23
manifests/recovery.xml Executable file
View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<recovery-update>
<version>1.0.2.2</version>
<build-date>2023-01-17</build-date>
<git>
<pinn>
<branch>master</branch>
<commit>206afc83287084783bead347f3e55a91de6d0fe7</commit>
</pinn>
<rpi-userland>
<branch>master</branch>
<commit>54fd97ae4066a10b6b02089bc769ceed328737e0</commit>
</rpi-userland>
<rpi-firmware>
<branch>master</branch>
<commit>78852e166b4cf3ebb31d051e996d54792f0994b0</commit>
</rpi-firmware>
<rpi-linux>
<branch>rpi-5.4.y</branch>
<commit>ec0dcf3064b8ba99f226438214407fcea9870f76</commit>
</rpi-linux>
</git>
</recovery-update>

26
repository.xml Executable file
View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<os-list>
<repository>
<distribution name="tononixOS Home Edition" description="Operating System for the tononixPC" group="General">
<version>1.0.0</version>
<download-size/>
<icon/>
<nominal-size/>
<release-date>2023-01-17</release-date>
<supported-model>Raspberry Pi 3 Model B+</supported-model>
<supported-model>Raspberry Pi 4</supported-model>
<supports-backup>false</supports-backup>
<url>
<os-info>https://tonoxisisle.services/tononixOS/os/os.json</os-info>
<marketing-info>http://tonoxisisle.services/tononixOS/os/marketing-info.tar.gz</marketing-info>
<partition-setup>https://tonoxisisle.services/tononixOS/os/partition_setup.sh</partition-setup>
<partition-info>https://tonoxisisle.services/tononixOS/os/partitions.json</partition-info>
<tarball>https://tonoxisisle.services/tononixOS/os/boot.tar.gz</tarball>
<tarball>https://tonoxisisle.services/tononixOS/os/root.tar.gz</tarball>
<tarball>https://tonoxisisle.services/tononixOS/os/editions/home/appfs.tar.gz</tarball>
</url>
</distribution>
</repository>
</os-list>

29
routes/osinfo-routes.php Executable file
View File

@@ -0,0 +1,29 @@
<?php
$app->delete('/os/{operatingSystem}', function ($request, $response, array $args) {
// Remove an operating system manifest
});
$app->patch('/os/{operatingSystem}', function ($request, $response, array $args) {
// Update an operating system manifest
});
$app->put('/os/{operatingSystem}', function ($request, $response, array $args) {
// Upload a new operating system manifest
});
$app->get('/os/{operatingSystem}/manifest', function ($request, $response, array $args) {
// Retrieve a new Operating System Manifest
});
$app->get('/os/{operatingSystem}/tarball/{tarball}', function ($request, $response, array $args) {
});
$app->get('/os/{operatingSystem}/partition-info', function ($request, $response, array $args) {
});
$app->get('/os/{operatingSystem}/partition-setup', function ($request, $response, array $args) {
});

74
routes/recovery-routes.php Executable file
View File

@@ -0,0 +1,74 @@
<?php
$app->map(["GET","POST"],'/recovery/version', function ($request, $response, array $args) {
global $GPGFingerprint;
global $secomm;
global $recoveryParser;
// This response is supposed to be in plain text, so we'll output as such.
$response = $response->withHeader("Content-Type","text/plain");
if($request->hasHeader("X-Secure-Recovery-Enabled") && $request->getHeader('X-Secure-Recovery-Enabled') == "true")
{
// Signed responses have been requested, we'll oblige.
$signedResponse = $secomm->sign($recoveryParser->getBootloaderUpdateManifest());
$response->getBody()->write($signedResponse);
return $response
->withHeader("X-Secure-Recovery-Fingerprint", $GPGFingerprint)
->withStatus(201);
} else {
$response->getBody()->write($recoveryParser->getBootloaderUpdateManifest());
return $response->withStatus(201);
}
});
$app->get('/recovery/ota-download', function ($request, $response, array $args) {
global $GPGFingerprint;
global $secomm;
global $recoveryParser;
// This response is supposed to be in plain text, so we'll output as such.
$response = $response->withHeader("Content-Type","text/plain");
// Store the device serial number and model name for use later.
$deviceSerialNumber = $request->getHeader("X-Device-Serial-Number");
$deviceModelName = $request->getHeader("X-Device-Model-Name");
// Check if we've got the secure recovery enable header.
if($request->hasHeader("X-Secure-Recovery-Enabled") && $request->getHeader('X-Secure-Recovery-Enabled') == "true")
{
// Signed responses have been requested, we'll oblige.
$signedResponse = $secomm->sign(file_get_contents($recoveryParser->getBootloaderUpdateOTA()));
$response->getBody()->write($signedResponse);
return $response
->withHeader("X-Secure-Recovery-Fingerprint", $GPGFingerprint)
->withStatus(201);
} else {
$response->getBody()->write(file_get_contents($recoveryParser->getBootloaderUpdateOTA()));
return $response->withStatus(201);
}
})->setName("ota-download");
$app->get('/recovery/repository', function ($request, $response, array $args) {
global $GPGFingerprint;
global $secomm;
global $recoveryParser;
// This response is supposed to be in plain text, so we'll output as such.
$response = $response->withHeader("Content-Type","application/json");
if($request->hasHeader("X-Secure-Recovery-Enabled") && $request->getHeader('X-Secure-Recovery-Enabled') == "true")
{
// Signed responses have been requested, we'll oblige.
$signedResponse = $secomm->sign($recoveryParser->getRecoveryInstallerRepository());
$response->getBody()->write($signedResponse);
return $response
->withHeader("X-Secure-Recovery-Fingerprint", $GPGFingerprint)
->withStatus(201);
} else {
$response->getBody()->write($recoveryParser->getRecoveryInstallerRepository());
return $response->withStatus(201);
}
});
$app->get('/recovery/repository/sources', function ($request, $response, array $args) {
});

301
templates/serverstatus.tpl Executable file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,29 @@
<Project Sdk="Peachpie.NET.Sdk/1.2.0-r15261">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netcoreapp6.0</TargetFramework>
<RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers>
</PropertyGroup>
<ItemGroup>
<Compile Include="**/*.php;**/*.phar;" Exclude="$(DefaultExcludeItems)" />
</ItemGroup>
<ItemGroup>
<Compile Remove="vendor\react\http\src\Server.php" />
<Compile Remove="vendor\**\**\Tests\*.php" />
<Compile Remove="vendor\**\**\Test\*.php" />
</ItemGroup>
<ItemGroup>
<None Include="vendor\react\http\src\Server.php" />
<None Include="vendor\**\**\Tests\*.php" />
<None Include="vendor\**\**\Test\*.php" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="gpgme-sharp" Version="2.0.3" />
<PackageReference Include="Peachpie.Library" Version="1.2.0-r15261" />
<PackageReference Include="Peachpie.Library.Scripting" Version="1.2.0-r15261" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="Peachpie.App" Version="1.2.0-r15261" />
<PackageReference Update="Peachpie.Runtime" Version="1.2.0-r15261" />
</ItemGroup>
</Project>

139
update.php Executable file
View File

@@ -0,0 +1,139 @@
<?php
/**
* tononixPC System Update Server
* Version 1.0.1 (Slim Rewrite)
*/
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
use Slim\Views\Twig;
use Slim\Views\TwigMiddleware;
use PharIO\GnuPG as GnuPG;
use Noodlehaus\Config;
use Noodlehaus\Parser\Properties;
use Slim\Factory\ServerRequestCreatorFactory;
define("ROOT_DIR", __DIR__);
require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/libUpdateService.php';
require __DIR__ . '/libSignedComms.php';
$AppConfiguration = new Config("config.prop", new Properties);
/** Check for GnuPG support
*
* First we check for the native PHP gnupg library, then we check if we're running under PeachPie and have Gpgme-sharp.
*
**/
if (function_exists("gnupg_verify") OR (class_exists('\Libgpgme\Gpgme') && defined("PEACHPIE_VERSION"))) {
// Lets's fine tune this detection a little more
// If we're in peachpie and the gpgme-sharp namespace is available, we obviously have it.
if(defined("PEACHPIE_VERSION") && class_exists('\Libgpgme\Gpgme')) {
$gnupg_support['state'] = true;
$gnupg_support['provider'] = "gpgme-sharp";
} elseif(function_exists("gnupg_verify")) {
// Likewise, if the gnupg_verify function exists, we have access to that as well.
$gnupg_support['state'] = true;
$gnupg_support['provider'] = "ext-gnupg";
}
//Check to see if we're running under PeachPie, that would explain not having it.
} elseif(defined("PEACHPIE_VERSION") && !class_exists('Libgpgme\Gpgme')) {
$gnupg_support['state'] = false;
$gnupg_support['reason'] = "was not compiled with GnuPG support.";
} elseif(!defined("PEACHPIE_VERSION") && function_exists("gnupg_verify")) {
$gnupg_support['state'] = false;
$gnupg_support['reason'] = "GnuPG extension is either not activated or not installed.";
} else {
$gnupg_support['state'] = false;
$gnupg_support['reason'] = "unable to determine reason.";
}
$AppConfiguration = $AppConfiguration->all();
if($AppConfiguration['update-server.debug'] == true)
{
define("DEBUG", 1);
}
$GPGKeyFile = $AppConfiguration['security.gpg-key-file'];
$GPGFingerprint = $AppConfiguration['security.gpg-fingerprint'];
$GPGHome = $AppConfiguration['security.gpg-key-tmp'];
$GPGEnabled = $AppConfiguration['security.gpg.enabled'];
$GPGPassphrase = $AppConfiguration['security.gpg-passphrase'];
$gnupg_support['enabled'] = $GPGEnabled;
if($gnupg_support['state'] == true && $GPGEnabled == true)
{
/** Unlike the reference version, we're just going to go ahead and pre-emptively set up the keyring. **/
if(!defined("DEBUG"))
{
$realPassphrase = file_get_contents($GPGPassphrase);
} else {
$realPassphrase = $GPGPassphrase;
}
//TODO: Fix $secomm = new SignedCommunicationProvider($GPGKeyFile, $GPGFingerprint, $realPassphrase, $GPGHome);
}
$recoveryParser = new RecoveryUpdateService();
$app = AppFactory::create();
$twig = Twig::create(__DIR__.'/templates');
$app->add(TwigMiddleware::create($app, $twig));
$errorMiddleware = $app->addErrorMiddleware(true, true, true);
include("routes/recovery-routes.php");
include("routes/osinfo-routes.php");
$app->any("/", function ($request, $response, array $args) {
global $AppConfiguration;
global $recoveryParser;
global $app;
global $gnupg_support;
$response = $response->withHeader("Content-Type","text/html")->withStatus(200);
if(defined("PEACHPIE_VERSION")) {
// If we're running a compiled copy, we should get the version number of the resulting assembly.
$Assembly = \System\Reflection\Assembly::GetExecutingAssembly();
$version = $Assembly->GetName()->Version->ToString();
} else {
$version = $AppConfiguration['update-server.version'];
}
$GPGKeyFile = $AppConfiguration['security.gpg-key-file'];
$GPGFingerprint = $AppConfiguration['security.gpg-fingerprint'];
$GPGHome = $AppConfiguration['security.gpg-key-tmp'];
$OSInfo = new OSUpdateService();
if(defined("PEACHPIE_VERSION"))
{
$runtime = "PeachPie/.NET ".PEACHPIE_VERSION;
} else {
$runtime = "PHP/Native ".phpversion();
}
$view = Twig::fromRequest($request);
$OSVersions = $OSInfo->GetAvailableOSList();
if($gnupg_support['state'] == true) {
$gnupg_support_string = "True (Engine: ".$gnupg_support['provider'].", Secure responses enabled: ".$gnupg_support['enabled'].")";
} else {
$gnupg_support_string = "False, ".$gnupg_support['reason'];
}
$bootloaderVersion = $recoveryParser->getBootloaderUpdateVersion();
$template = $view->render($response, 'serverstatus.tpl', [
'version' => $version,
'runtime' => $runtime,
'gnupg_support' => $gnupg_support_string,
'gnupg_fingerprint' => $GPGFingerprint,
'gnupg_home' => $GPGHome,
'gnupg_certificate' => $GPGKeyFile,
'tononixOSBootloaderVersion' => $bootloaderVersion,
'OSList' => $OSVersions
]);
$test = $template;
return $template;
});
$app->run();