Files
IKAR/tool/verify_argus_release.dart
T

107 lines
4.0 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:crypto/crypto.dart';
Future<void> main(List<String> args) async {
final version = args.isEmpty ? '1.0.0' : args[0];
final code = args.length < 2 ? 1 : int.parse(args[1]);
final fileName = 'ikar-$version+$code.apk';
final reportSuffix = code == 1 ? '' : '-$version';
final base = Uri.parse('https://argus.kusoft.xyz');
final http = HttpClient()..connectionTimeout = const Duration(seconds: 15);
final local = File('artifacts/$fileName');
final downloaded = File('artifacts/ikar-$version+$code.argus.apk');
try {
Future<dynamic> getJson(String path) async {
final response = await (await http.getUrl(
base.resolve(path),
)).close().timeout(const Duration(seconds: 20));
if (response.statusCode != 200) {
throw StateError('HTTP ${response.statusCode} for $path');
}
return jsonDecode(
await utf8.decodeStream(response).timeout(const Duration(seconds: 20)),
);
}
final results = await Future.wait([
getJson('/api/apps/ikar/manifest?platform=android&channel=stable'),
getJson('/api/apps'),
getJson('/health'),
]);
final manifest = results[0] as Map<String, dynamic>;
final release = manifest['release'] as Map<String, dynamic>;
if (manifest['slug'] != 'ikar' ||
manifest['name'] != 'ИКАР' ||
release['version'] != version ||
release['androidVersionCode'] != code ||
release['platform'] != 'android' ||
release['channel'] != 'stable' ||
release['packageKind'] != 'apk' ||
release['originalFileName'] != fileName) {
throw StateError('Incorrect public application or release identity');
}
final catalog = results[1] as List;
final listed = catalog.singleWhere((app) => app['slug'] == 'ikar');
if (listed['name'] != 'ИКАР' ||
listed['latestRelease']['id'] != release['id'] ||
(results[2] as Map)['status'] != 'ok') {
throw StateError('Catalog or service health verification failed');
}
final uri = base.resolve(release['downloadPath'] as String);
if (uri.scheme != 'https' ||
uri.host != base.host ||
!uri.path.startsWith('/api/apps/ikar/releases/')) {
throw StateError('Unexpected public download address');
}
final response = await (await http.getUrl(
uri,
)).close().timeout(const Duration(seconds: 20));
if (response.statusCode != 200 ||
response.headers.contentType?.mimeType !=
'application/vnd.android.package-archive') {
throw StateError('APK download unavailable');
}
await response
.timeout(const Duration(seconds: 30))
.pipe(downloaded.openWrite());
final localDigest = (await sha256.bind(local.openRead()).first).toString();
final publicDigest = (await sha256.bind(downloaded.openRead()).first)
.toString();
final size = await downloaded.length();
if (publicDigest != localDigest ||
publicDigest != release['sha256'] ||
size != await local.length() ||
size != release['packageSizeBytes']) {
throw StateError('Public APK differs from the verified local artifact');
}
final report = {
'passed': true,
'slug': 'ikar',
'name': 'ИКАР',
'package': 'ru.ikar.app',
'version': version,
'androidVersionCode': code,
'releaseId': release['id'],
'sha256': publicDigest,
'bytes': size,
'tlsValidation': true,
'listedInCatalog': true,
'serviceHealth': 'ok',
'cardUrl': 'https://argus.kusoft.xyz/#app=ikar',
'downloadUrl': uri.toString(),
'timestamp': DateTime.now().toUtc().toIso8601String(),
};
const encoder = JsonEncoder.withIndent(' ');
await File(
'artifacts/argus-manifest$reportSuffix.json',
).writeAsString(encoder.convert(manifest));
await File(
'artifacts/argus-public-verification$reportSuffix.json',
).writeAsString(encoder.convert(report));
stdout.writeln(jsonEncode(report));
} finally {
http.close(force: true);
}
}