hardware acceleration and support for NVIDIA cards on nonfree images (#3089)

* add nvidia packages

* add nvidia deps to nonfree

* gpu_acceleration flag & nvidia hacking

* fix gpu_config & /tmp/lxc.log

* implement hardware acceleration more dynamically

* refactor OpenUI

* use mknod

* registry updates for multi-hardware-requirements

* pluralize

* handle new registry types

* remove log

* migrations and driver fixes

* wip

* misc patches

* handle nvidia-container differently

* chore: comments (#3093)

* chore: comments

* revert some sizing

---------

Co-authored-by: Matt Hill <mattnine@protonmail.com>

* Revert "handle nvidia-container differently"

This reverts commit d708ae53df.

* fix debian containers

* cleanup

* feat: add empty array placeholder in forms (#3095)

* fixes from testing, client side device filtering for better fingerprinting resistance

* fix mac builds

---------

Co-authored-by: Sam Sartor <me@samsartor.com>
Co-authored-by: Matt Hill <mattnine@protonmail.com>
Co-authored-by: Alex Inkin <alexander@inkin.ru>
This commit is contained in:
Aiden McClelland
2026-01-15 11:42:17 -08:00
committed by GitHub
parent e8ef39adad
commit 99871805bd
95 changed files with 2758 additions and 1092 deletions

View File

@@ -24,7 +24,7 @@ import { MarketplaceItemComponent } from './item.component'
icon=""
/>
<!-- release date -->
@if (pkg().s9pk?.publishedAt; as published) {
@if (pkg().s9pks[0]?.[1]?.publishedAt; as published) {
<marketplace-item
[style.pointer-events]="'none'"
[data]="(published | date: 'medium')!"

View File

@@ -72,8 +72,8 @@ export class FilterPackagesPipe implements PipeTransform {
.filter(p => category === 'all' || p.categories.includes(category!))
.sort((a, b) => {
return (
new Date(b.s9pk.publishedAt).valueOf() -
new Date(a.s9pk.publishedAt).valueOf()
new Date(b.s9pks[0]?.[1].publishedAt!).valueOf() -
new Date(a.s9pks[0]?.[1].publishedAt!).valueOf()
)
})
.map(a => ({ ...a }))

View File

@@ -32,10 +32,7 @@ export type StoreData = {
packages: MarketplacePkg[]
}
export type MarketplacePkgBase = OptionalProperty<
T.PackageVersionInfo,
's9pk'
> & {
export type MarketplacePkgBase = T.PackageVersionInfo & {
id: T.PackageId
version: string
flavor: string | null

View File

@@ -1,10 +1,9 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { ChangeDetectionStrategy, Component } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop'
import { ActivatedRoute } from '@angular/router'
import { TuiDialogContext, TuiLoader, TuiNotification } from '@taiga-ui/core'
import { TuiLoader, TuiNotification } from '@taiga-ui/core'
import { NgDompurifyPipe } from '@taiga-ui/dompurify'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { catchError, ignoreElements, Observable, of } from 'rxjs'
import { catchError, ignoreElements, Observable, of, share } from 'rxjs'
import { SafeLinksDirective } from '../directives/safe-links.directive'
import { MarkdownPipe } from '../pipes/markdown.pipe'
import { getErrorMessage } from '../services/error.service'
@@ -34,7 +33,10 @@ import { getErrorMessage } from '../services/error.service'
],
})
export class MarkdownComponent {
protected readonly data = injectContext<{ data: Observable<string> }>().data
private readonly data = injectContext<{
data: Observable<string>
}>().data.pipe(share())
protected readonly content = toSignal<string>(this.data)
protected readonly error = toSignal(
this.data.pipe(

View File

@@ -5,11 +5,14 @@ import {
InjectionToken,
input,
} from '@angular/core'
import { TuiHintDirective } from '@taiga-ui/core'
import { i18nPipe } from '../i18n/i18n.pipe'
export const VERSION = new InjectionToken<string>('VERSION')
@Directive({
selector: '[docsLink]',
hostDirectives: [TuiHintDirective],
host: {
target: '_blank',
rel: 'noreferrer',
@@ -20,12 +23,18 @@ export class DocsLinkDirective {
private readonly version = inject(VERSION)
readonly path = input.required<string>()
readonly fragment = input<string>('')
protected readonly url = computed(() => {
const path = this.path()
const relative = path.startsWith('/') ? path : `/${path}`
return `https://docs.start9.com${relative}?os=${this.version}${this.fragment()}`
})
constructor() {
inject(TuiHintDirective).content.set(
inject(i18nPipe).transform('Documentation'),
)
}
}

View File

@@ -596,4 +596,5 @@ export default {
626: 'Hochladen',
627: 'UI öffnen',
628: 'In Zwischenablage kopiert',
629: 'Die Liste ist leer',
} satisfies i18n

View File

@@ -595,4 +595,5 @@ export const ENGLISH = {
'Upload': 626, // as in, upload a file
'Open UI': 627, // as in, upload a file
'Copied to clipboard': 628,
} as const
'The list is empty': 629,
} as Record<any, any>

View File

@@ -596,4 +596,5 @@ export default {
626: 'Subir',
627: 'Abrir UI',
628: 'Copiado al portapapeles',
629: 'La lista está vacía',
} satisfies i18n

View File

@@ -596,4 +596,5 @@ export default {
626: 'Téléverser',
627: 'Ouvrir UI',
628: 'Copié dans le presse-papiers',
629: 'La liste est vide',
} satisfies i18n

View File

@@ -596,4 +596,5 @@ export default {
626: 'Prześlij',
627: 'Otwórz UI',
628: 'Skopiowano do schowka',
629: 'Lista jest pusta',
} satisfies i18n

View File

@@ -20,6 +20,7 @@ import { tuiObfuscateOptionsProvider } from '@taiga-ui/cdk'
import {
TUI_DATE_FORMAT,
TUI_DIALOGS_CLOSE,
TUI_MEDIA,
tuiAlertOptionsProvider,
tuiButtonOptionsProvider,
tuiDropdownOptionsProvider,
@@ -140,4 +141,12 @@ export const APP_PROVIDERS = [
none: identity,
},
}),
{
provide: TUI_MEDIA,
useValue: {
mobile: 1000,
desktopSmall: 1280,
desktopLarge: Infinity,
},
},
]

View File

@@ -80,7 +80,7 @@ export interface FormContext<T> {
margin: 1rem -1px -1rem;
gap: 1rem;
background: var(--tui-background-elevation-1);
border-top: 1px solid var(--tui-background-base-alt);
box-shadow: inset 0 1px var(--tui-background-neutral-1);
}
`,
imports: [

View File

@@ -87,6 +87,8 @@ import { FormObjectComponent } from './object.component'
}
</div>
</div>
} @empty {
<div class="placeholder">{{ 'The list is empty' | i18n }}</div>
}
`,
styles: `
@@ -99,8 +101,8 @@ import { FormObjectComponent } from './object.component'
.label {
display: flex;
font-size: 1.25rem;
font-weight: bold;
align-items: center;
font: var(--tui-font-heading-6);
}
.add {
@@ -157,6 +159,17 @@ import { FormObjectComponent } from './object.component'
animation-name: tuiFade, tuiCollapse;
}
}
.placeholder {
display: flex;
align-items: center;
justify-content: center;
height: var(--tui-height-m);
color: var(--tui-text-tertiary);
border-radius: var(--tui-radius-m);
border: 1px dashed var(--tui-background-neutral-1);
margin: 0.5rem 0 0;
}
`,
hostDirectives: [ControlDirective],
imports: [

View File

@@ -1,12 +1,11 @@
import { Component } from '@angular/core'
import { Component, computed } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { invert } from '@start9labs/shared'
import { IST } from '@start9labs/start-sdk'
import { tuiPure } from '@taiga-ui/cdk'
import { TuiIcon, TuiTextfield } from '@taiga-ui/core'
import { TuiChevron, TuiMultiSelect, TuiTooltip } from '@taiga-ui/kit'
import { Control } from './control'
import { HintPipe } from '../pipes/hint.pipe'
import { Control } from './control'
@Component({
selector: 'form-multiselect',
@@ -21,7 +20,8 @@ import { HintPipe } from '../pipes/hint.pipe'
[disabled]="disabled"
[readOnly]="readOnly"
[items]="items"
[(ngModel)]="selected"
[ngModel]="selected()"
(ngModelChange)="onSelected($event)"
(blur)="control.onTouched()"
></select>
@if (spec | hint; as hint) {
@@ -58,30 +58,26 @@ export class FormMultiselectComponent extends Control<
private readonly isExceedingLimit = (item: string) =>
!!this.spec.maxLength &&
this.selected.length >= this.spec.maxLength &&
!this.selected.includes(item)
this.selected().length >= this.spec.maxLength &&
!this.selected().includes(item)
readonly disabledItemHandler = (item: string): boolean =>
this.isDisabled(item) || this.isExceedingLimit(item)
readonly items = Object.values(this.spec.values)
readonly selected = computed(
() =>
this.control.value().map((key: string) => this.spec.values[key] || '') ||
[],
)
get disabled(): boolean {
return typeof this.spec.disabled === 'string'
}
get selected(): string[] {
return this.memoize(this.value)
}
set selected(value: string[]) {
onSelected(value: string[]) {
this.value = Object.entries(this.spec.values)
.filter(([_, v]) => value.includes(v))
.map(([k]) => k)
}
@tuiPure
private memoize(value: null | readonly string[]): string[] {
return value?.map(key => this.spec.values[key] || '') || []
}
}

View File

@@ -111,11 +111,11 @@ import { ConfigService } from 'src/app/services/config.service'
&-list {
display: grid;
grid-template-columns: repeat(1, minmax(0, 1fr));
gap: 4rem 3rem;
gap: 3.5rem 2.5rem;
padding: 1.5rem;
@media (min-width: 768px) {
padding: 2rem;
grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (min-width: 1024px) {
grid-template-columns: repeat(2, minmax(0, 1fr));

View File

@@ -10,7 +10,7 @@ import {
} from '@start9labs/shared'
import { TuiCell } from '@taiga-ui/layout'
import { PatchDB } from 'patch-db-client'
import { from, map } from 'rxjs'
import { defer, map } from 'rxjs'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { DataModel } from 'src/app/services/patch-db/data-model'
import { getManifest } from 'src/app/utils/get-package-data'
@@ -54,14 +54,13 @@ import {
})
export default class ServiceAboutRoute {
private readonly pkgId = getPkgId()
private readonly api = inject(ApiService)
private readonly copyService = inject(CopyService)
private readonly markdown = inject(DialogService).openComponent(MARKDOWN, {
label: 'License',
size: 'l',
data: from(
inject(ApiService).getStatic(
`/s9pk/installed/${this.pkgId}.s9pk/LICENSE.md`,
),
data: defer(() =>
this.api.getStatic([`/s9pk/installed/${this.pkgId}.s9pk/LICENSE.md`], {}),
),
})

View File

@@ -41,6 +41,7 @@ async function parseS9pk(file: File): Promise<MarketplacePkgSideload> {
sourceVersion: s9pk.manifest.canMigrateFrom,
flavor: ExtendedVersion.parse(s9pk.manifest.version).flavor,
fullLicense: await s9pk.license(),
s9pks: [],
}
}

View File

@@ -70,7 +70,7 @@ import UpdatesComponent from './updates.component'
</div>
</td>
<td class="desktop">{{ item().gitHash }}</td>
<td class="desktop">{{ item().s9pk.publishedAt | date }}</td>
<td class="desktop">{{ item().s9pks[0]?.[1]?.publishedAt | date }}</td>
<td>
<button
tuiIconButton
@@ -114,7 +114,9 @@ import UpdatesComponent from './updates.component'
</p>
<p tuiTitle class="mobile">
<b>{{ 'Published' | i18n }}</b>
<span tuiSubtitle>{{ item().s9pk.publishedAt | date }}</span>
<span tuiSubtitle>
{{ item().s9pks[0]?.[1]?.publishedAt | date }}
</span>
</p>
<p tuiTitle>
<span>

View File

@@ -40,7 +40,9 @@ export namespace Mock {
squashfs: {
aarch64: {
publishedAt: '2025-03-21T23:55:29.583006392Z',
url: 'https://alpha-registry-x.start9.com/startos/v0.3.6-alpha.17/startos-0.3.6-alpha.17-b8ff331~dev_aarch64.squashfs',
urls: [
'https://alpha-registry-x.start9.com/startos/v0.3.6-alpha.17/startos-0.3.6-alpha.17-b8ff331~dev_aarch64.squashfs',
],
commitment: {
hash: 'OUnANnZePtf7rSbj38JESl+iJAV0z0aiZ4opCiwpGbo=',
size: 1331900416,
@@ -52,7 +54,9 @@ export namespace Mock {
},
'aarch64-nonfree': {
publishedAt: '2025-03-21T23:56:38.299572946Z',
url: 'https://alpha-registry-x.start9.com/startos/v0.3.6-alpha.17/startos-0.3.6-alpha.17-b8ff331~dev_aarch64-nonfree.squashfs',
urls: [
'https://alpha-registry-x.start9.com/startos/v0.3.6-alpha.17/startos-0.3.6-alpha.17-b8ff331~dev_aarch64-nonfree.squashfs',
],
commitment: {
hash: '6k+0RcyRQV+5A+h06OqpHxd4IT6IlFkfdy9dfHIP90c=',
size: 1641500672,
@@ -64,7 +68,9 @@ export namespace Mock {
},
raspberrypi: {
publishedAt: '2025-03-22T00:08:17.083064390Z',
url: 'https://alpha-registry-x.start9.com/startos/v0.3.6-alpha.17/startos-0.3.6-alpha.17-b8ff331~dev_raspberrypi.squashfs',
urls: [
'https://alpha-registry-x.start9.com/startos/v0.3.6-alpha.17/startos-0.3.6-alpha.17-b8ff331~dev_raspberrypi.squashfs',
],
commitment: {
hash: 'K+XuTZxo1KVsKjNSV8PPOMruCvAEZwerF9mbpFl53Gk=',
size: 1544417280,
@@ -76,7 +82,9 @@ export namespace Mock {
},
x86_64: {
publishedAt: '2025-03-22T00:05:57.684319247Z',
url: 'https://alpha-registry-x.start9.com/startos/v0.3.6-alpha.17/startos-0.3.6-alpha.17-b8ff331~dev_x86_64.squashfs',
urls: [
'https://alpha-registry-x.start9.com/startos/v0.3.6-alpha.17/startos-0.3.6-alpha.17-b8ff331~dev_x86_64.squashfs',
],
commitment: {
hash: '3UVkx3TQMBPlSU1OnV48Om9vjjA3s+Nk6dX3auYGpBo=',
size: 1424007168,
@@ -88,7 +96,9 @@ export namespace Mock {
},
'x86_64-nonfree': {
publishedAt: '2025-03-22T00:07:11.893777122Z',
url: 'https://alpha-registry-x.start9.com/startos/v0.3.6-alpha.17/startos-0.3.6-alpha.17-b8ff331~dev_x86_64-nonfree.squashfs',
urls: [
'https://alpha-registry-x.start9.com/startos/v0.3.6-alpha.17/startos-0.3.6-alpha.17-b8ff331~dev_x86_64-nonfree.squashfs',
],
commitment: {
hash: 'IS1gJ56n/HlQqFbl1upMOAtLxyxB0cY0H89Ha+9h1lE=',
size: 1743425536,
@@ -110,7 +120,9 @@ export namespace Mock {
squashfs: {
aarch64: {
publishedAt: '2025-04-21T20:58:48.140749883Z',
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.14/startos-0.4.0-alpha.14-33ae46f~dev_aarch64.squashfs',
urls: [
'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.14/startos-0.4.0-alpha.14-33ae46f~dev_aarch64.squashfs',
],
commitment: {
hash: '4elBFVkd/r8hNadKmKtLIs42CoPltMvKe2z3LRqkphk=',
size: 1343500288,
@@ -122,7 +134,9 @@ export namespace Mock {
},
'aarch64-nonfree': {
publishedAt: '2025-04-21T21:07:00.249285116Z',
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.14/startos-0.4.0-alpha.14-33ae46f~dev_aarch64-nonfree.squashfs',
urls: [
'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.14/startos-0.4.0-alpha.14-33ae46f~dev_aarch64-nonfree.squashfs',
],
commitment: {
hash: 'MrCEi4jxbmPS7zAiGk/JSKlMsiuKqQy6RbYOxlGHOIQ=',
size: 1653075968,
@@ -134,7 +148,9 @@ export namespace Mock {
},
raspberrypi: {
publishedAt: '2025-04-21T21:16:12.933319237Z',
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.14/startos-0.4.0-alpha.14-33ae46f~dev_raspberrypi.squashfs',
urls: [
'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.14/startos-0.4.0-alpha.14-33ae46f~dev_raspberrypi.squashfs',
],
commitment: {
hash: '/XTVQRCqY3RK544PgitlKu7UplXjkmzWoXUh2E4HCw0=',
size: 1490731008,
@@ -146,7 +162,9 @@ export namespace Mock {
},
x86_64: {
publishedAt: '2025-04-21T21:14:20.246908903Z',
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.14/startos-0.4.0-alpha.14-33ae46f~dev_x86_64.squashfs',
urls: [
'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.14/startos-0.4.0-alpha.14-33ae46f~dev_x86_64.squashfs',
],
commitment: {
hash: '/6romKTVQGSaOU7FqSZdw0kFyd7P+NBSYNwM3q7Fe44=',
size: 1411657728,
@@ -158,7 +176,9 @@ export namespace Mock {
},
'x86_64-nonfree': {
publishedAt: '2025-04-21T21:15:17.955265284Z',
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.14/startos-0.4.0-alpha.14-33ae46f~dev_x86_64-nonfree.squashfs',
urls: [
'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.14/startos-0.4.0-alpha.14-33ae46f~dev_x86_64-nonfree.squashfs',
],
commitment: {
hash: 'HCRq9sr/0t85pMdrEgNBeM4x11zVKHszGnD1GDyZbSE=',
size: 1731035136,
@@ -234,6 +254,7 @@ export namespace Mock {
source: 'packed',
arch: ['x86_64', 'aarch64'],
emulateMissingAs: 'aarch64',
nvidiaContainer: false,
},
},
volumes: ['main'],
@@ -242,6 +263,7 @@ export namespace Mock {
arch: null,
ram: null,
},
hardwareAcceleration: false,
}
export const MockManifestLnd: T.Manifest = {
@@ -292,6 +314,7 @@ export namespace Mock {
source: 'packed',
arch: ['x86_64', 'aarch64'],
emulateMissingAs: 'aarch64',
nvidiaContainer: false,
},
},
volumes: ['main'],
@@ -300,6 +323,7 @@ export namespace Mock {
arch: null,
ram: null,
},
hardwareAcceleration: false,
}
export const MockManifestBitcoinProxy: T.Manifest = {
@@ -343,6 +367,7 @@ export namespace Mock {
source: 'packed',
arch: ['x86_64', 'aarch64'],
emulateMissingAs: 'aarch64',
nvidiaContainer: false,
},
},
volumes: ['main'],
@@ -351,6 +376,7 @@ export namespace Mock {
arch: null,
ram: null,
},
hardwareAcceleration: false,
}
export const BitcoinDep: T.DependencyMetadata = {
@@ -376,7 +402,6 @@ export namespace Mock {
'26.1.0:0.1.0': {
title: 'Bitcoin Core',
description: mockDescription,
hardwareRequirements: { arch: null, device: [], ram: null },
license: 'mit',
wrapperRepo: 'https://github.com/start9labs/bitcoind-startos',
upstreamRepo: 'https://github.com/bitcoin/bitcoin',
@@ -398,12 +423,20 @@ export namespace Mock {
stop: 'test',
restore: 'test',
},
s9pk: {
url: 'https://github.com/Start9Labs/bitcoind-startos/releases/download/v26.1.0/bitcoind.s9pk',
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
s9pks: [
[
{ arch: null, device: [], ram: null },
{
urls: [
'https://github.com/Start9Labs/bitcoind-startos/releases/download/v26.1.0/bitcoind.s9pk',
],
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
],
],
hardwareAcceleration: false,
},
'#knots:26.1.20240325:0': {
title: 'Bitcoin Knots',
@@ -411,7 +444,6 @@ export namespace Mock {
short: 'An alternate fully verifying implementation of Bitcoin',
long: 'Bitcoin Knots is a combined Bitcoin node and wallet. Not only is it easy to use, but it also ensures bitcoins you receive are both real bitcoins and really yours.',
},
hardwareRequirements: { arch: null, device: [], ram: null },
license: 'mit',
wrapperRepo: 'https://github.com/start9labs/bitcoinknots-startos',
upstreamRepo: 'https://github.com/bitcoinknots/bitcoin',
@@ -433,12 +465,20 @@ export namespace Mock {
stop: 'test',
restore: 'test',
},
s9pk: {
url: 'https://github.com/Start9Labs/bitcoinknots-startos/releases/download/v26.1.20240513/bitcoind.s9pk',
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
s9pks: [
[
{ arch: null, device: [], ram: null },
{
urls: [
'https://github.com/Start9Labs/bitcoinknots-startos/releases/download/v26.1.20240513/bitcoind.s9pk',
],
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
],
],
hardwareAcceleration: false,
},
},
categories: ['bitcoin', 'featured'],
@@ -456,7 +496,6 @@ export namespace Mock {
'26.1.0:0.1.0': {
title: 'Bitcoin Core',
description: mockDescription,
hardwareRequirements: { arch: null, device: [], ram: null },
license: 'mit',
wrapperRepo: 'https://github.com/start9labs/bitcoind-startos',
upstreamRepo: 'https://github.com/bitcoin/bitcoin',
@@ -478,12 +517,20 @@ export namespace Mock {
stop: 'test',
restore: 'test',
},
s9pk: {
url: 'https://github.com/Start9Labs/bitcoind-startos/releases/download/v26.1.0/bitcoind.s9pk',
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
s9pks: [
[
{ arch: null, device: [], ram: null },
{
urls: [
'https://github.com/Start9Labs/bitcoind-startos/releases/download/v26.1.0/bitcoind.s9pk',
],
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
],
],
hardwareAcceleration: false,
},
'#knots:26.1.20240325:0': {
title: 'Bitcoin Knots',
@@ -491,7 +538,6 @@ export namespace Mock {
short: 'An alternate fully verifying implementation of Bitcoin',
long: 'Bitcoin Knots is a combined Bitcoin node and wallet. Not only is it easy to use, but it also ensures bitcoins you receive are both real bitcoins and really yours.',
},
hardwareRequirements: { arch: null, device: [], ram: null },
license: 'mit',
wrapperRepo: 'https://github.com/start9labs/bitcoinknots-startos',
upstreamRepo: 'https://github.com/bitcoinknots/bitcoin',
@@ -513,12 +559,20 @@ export namespace Mock {
stop: 'test',
restore: 'test',
},
s9pk: {
url: 'https://github.com/Start9Labs/bitcoinknots-startos/releases/download/v26.1.20240513/bitcoind.s9pk',
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
s9pks: [
[
{ arch: null, device: [], ram: null },
{
urls: [
'https://github.com/Start9Labs/bitcoinknots-startos/releases/download/v26.1.20240513/bitcoind.s9pk',
],
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
],
],
hardwareAcceleration: false,
},
},
categories: ['bitcoin', 'featured'],
@@ -538,7 +592,6 @@ export namespace Mock {
'0.17.5:0': {
title: 'LND',
description: mockDescription,
hardwareRequirements: { arch: null, device: [], ram: null },
license: 'mit',
wrapperRepo: 'https://github.com/start9labs/lnd-startos',
upstreamRepo: 'https://github.com/lightningnetwork/lnd',
@@ -563,12 +616,20 @@ export namespace Mock {
stop: 'test',
restore: 'test',
},
s9pk: {
url: 'https://github.com/Start9Labs/lnd-startos/releases/download/v0.17.5/lnd.s9pk',
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
s9pks: [
[
{ arch: null, device: [], ram: null },
{
urls: [
'https://github.com/Start9Labs/lnd-startos/releases/download/v0.17.5/lnd.s9pk',
],
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
],
],
hardwareAcceleration: false,
},
},
categories: ['lightning'],
@@ -586,7 +647,6 @@ export namespace Mock {
'0.17.4-beta:1.0-alpha': {
title: 'LND',
description: mockDescription,
hardwareRequirements: { arch: null, device: [], ram: null },
license: 'mit',
wrapperRepo: 'https://github.com/start9labs/lnd-startos',
upstreamRepo: 'https://github.com/lightningnetwork/lnd',
@@ -611,12 +671,20 @@ export namespace Mock {
stop: 'test',
restore: 'test',
},
s9pk: {
url: 'https://github.com/Start9Labs/lnd-startos/releases/download/v0.17.4/lnd.s9pk',
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
s9pks: [
[
{ arch: null, device: [], ram: null },
{
urls: [
'https://github.com/Start9Labs/lnd-startos/releases/download/v0.17.4/lnd.s9pk',
],
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
],
],
hardwareAcceleration: false,
},
},
categories: ['lightning'],
@@ -638,7 +706,6 @@ export namespace Mock {
'27.0.0:1.0.0': {
title: 'Bitcoin Core',
description: mockDescription,
hardwareRequirements: { arch: null, device: [], ram: null },
license: 'mit',
wrapperRepo: 'https://github.com/start9labs/bitcoind-startos',
upstreamRepo: 'https://github.com/bitcoin/bitcoin',
@@ -660,12 +727,20 @@ export namespace Mock {
stop: 'test',
restore: 'test',
},
s9pk: {
url: 'https://github.com/Start9Labs/bitcoind-startos/releases/download/v27.0.0/bitcoind.s9pk',
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
s9pks: [
[
{ arch: null, device: [], ram: null },
{
urls: [
'https://github.com/Start9Labs/bitcoind-startos/releases/download/v27.0.0/bitcoind.s9pk',
],
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
],
],
hardwareAcceleration: false,
},
'#knots:27.1.0:0': {
title: 'Bitcoin Knots',
@@ -673,7 +748,6 @@ export namespace Mock {
short: 'An alternate fully verifying implementation of Bitcoin',
long: 'Bitcoin Knots is a combined Bitcoin node and wallet. Not only is it easy to use, but it also ensures bitcoins you receive are both real bitcoins and really yours.',
},
hardwareRequirements: { arch: null, device: [], ram: null },
license: 'mit',
wrapperRepo: 'https://github.com/start9labs/bitcoinknots-startos',
upstreamRepo: 'https://github.com/bitcoinknots/bitcoin',
@@ -695,12 +769,20 @@ export namespace Mock {
stop: 'test',
restore: 'test',
},
s9pk: {
url: 'https://github.com/Start9Labs/bitcoinknots-startos/releases/download/v26.1.20240513/bitcoind.s9pk',
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
s9pks: [
[
{ arch: null, device: [], ram: null },
{
urls: [
'https://github.com/Start9Labs/bitcoinknots-startos/releases/download/v26.1.20240513/bitcoind.s9pk',
],
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
],
],
hardwareAcceleration: false,
},
},
categories: ['bitcoin', 'featured'],
@@ -718,7 +800,6 @@ export namespace Mock {
'0.18.0:0.0.1': {
title: 'LND',
description: mockDescription,
hardwareRequirements: { arch: null, device: [], ram: null },
license: 'mit',
wrapperRepo: 'https://github.com/start9labs/lnd-startos',
upstreamRepo: 'https://github.com/lightningnetwork/lnd',
@@ -743,12 +824,20 @@ export namespace Mock {
stop: 'test',
restore: 'test',
},
s9pk: {
url: 'https://github.com/Start9Labs/lnd-startos/releases/download/v0.18.0.1/lnd.s9pk',
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
s9pks: [
[
{ arch: null, device: [], ram: null },
{
urls: [
'https://github.com/Start9Labs/lnd-startos/releases/download/v0.18.0.1/lnd.s9pk',
],
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
],
],
hardwareAcceleration: false,
},
},
categories: ['lightning'],
@@ -766,7 +855,6 @@ export namespace Mock {
'0.3.2.7:0': {
title: 'Bitcoin Proxy',
description: mockDescription,
hardwareRequirements: { arch: null, device: [], ram: null },
license: 'mit',
wrapperRepo: 'https://github.com/Start9Labs/btc-rpc-proxy-wrappers',
upstreamRepo: 'https://github.com/Kixunil/btc-rpc-proxy',
@@ -790,12 +878,20 @@ export namespace Mock {
stop: 'test',
restore: 'test',
},
s9pk: {
url: 'https://github.com/Start9Labs/btc-rpc-proxy-startos/releases/download/v0.3.2.7/btc-rpc-proxy.s9pk',
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
s9pks: [
[
{ arch: null, device: [], ram: null },
{
urls: [
'https://github.com/Start9Labs/btc-rpc-proxy-startos/releases/download/v0.3.2.7/btc-rpc-proxy.s9pk',
],
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
],
],
hardwareAcceleration: false,
},
},
categories: ['bitcoin'],

View File

@@ -10,13 +10,11 @@ export abstract class ApiService {
abstract uploadFile(guid: string, body: Blob): Promise<void>
// for getting static files: ex license
abstract getStaticProxy(
pkg: MarketplacePkg,
path: 'LICENSE.md',
abstract getStatic(
urls: string[],
params: Record<string, string | number>,
): Promise<string>
abstract getStatic(url: string): Promise<string>
// websocket
abstract openWebsocket$<T>(

View File

@@ -45,29 +45,22 @@ export class LiveApiService extends ApiService {
// for getting static files: ex: license
async getStaticProxy(
pkg: MarketplacePkg,
path: 'LICENSE.md',
async getStatic(
urls: string[],
params: Record<string, string | number>,
): Promise<string> {
const encodedUrl = encodeURIComponent(pkg.s9pk.url)
return this.httpRequest({
method: 'GET',
url: `/s9pk/proxy/${encodedUrl}/${path}`,
params: {
rootSighash: pkg.s9pk.commitment.rootSighash,
rootMaxsize: pkg.s9pk.commitment.rootMaxsize,
},
responseType: 'text',
})
}
async getStatic(url: string): Promise<string> {
return this.httpRequest({
method: 'GET',
url,
responseType: 'text',
})
for (let url in urls) {
try {
const res = await this.httpRequest<string>({
method: 'GET',
url,
params,
responseType: 'text',
})
return res
} catch (e) {}
}
throw new Error('Could not fetch static file')
}
// websocket

View File

@@ -75,19 +75,14 @@ export class MockApiService extends ApiService {
await pauseFor(2000)
}
async getStaticProxy(
pkg: MarketplacePkg,
path: 'LICENSE.md',
async getStatic(
urls: string[],
params: Record<string, string | number>,
): Promise<string> {
await pauseFor(2000)
return markdown
}
async getStatic(url: string): Promise<string> {
await pauseFor(2000)
return markdown
}
// websocket
openWebsocket$<T>(

View File

@@ -142,7 +142,23 @@ export class MarketplaceService {
}
fetchStatic$(pkg: MarketplacePkg): Observable<string> {
return from(this.api.getStaticProxy(pkg, 'LICENSE.md'))
const registryAsset = pkg.s9pks[0]?.[1]
if (!registryAsset) {
throw new Error('No s9pk')
}
const urls =
registryAsset.urls.map(
u => `/s9pk/proxy/${encodeURIComponent(u)}/LICENSE.md`,
) || []
return from(
this.api.getStatic(urls, {
rootSighash: registryAsset.commitment.rootSighash,
rootMaxsize: registryAsset.commitment.rootMaxsize,
}),
)
}
private fetchRegistry$(url: string): Observable<StoreDataWithUrl | null> {