diff --git a/README.md b/README.md
index 8383498b1..e9d71401a 100644
--- a/README.md
+++ b/README.md
@@ -48,7 +48,8 @@
## Running StartOS
-There are multiple ways to get started with StartOS:
+> [!WARNING]
+> StartOS is in beta. It lacks features. It doesn't always work perfectly. Start9 servers are not plug and play. Using them properly requires some effort and patience. Please do not use StartOS or purchase a server if you are unable or unwilling to follow instructions and learn new concepts.
### 💰 Buy a Start9 server
This is the most convenient option. Simply [buy a server](https://store.start9.com) from Start9 and plug it in.
diff --git a/core/startos/src/registry/admin.rs b/core/startos/src/registry/admin.rs
index 968393e4e..50f83dc5c 100644
--- a/core/startos/src/registry/admin.rs
+++ b/core/startos/src/registry/admin.rs
@@ -74,12 +74,14 @@ async fn do_upload(
mut url: Url,
user: &str,
pass: &str,
+ pkg_id: &str,
body: Body,
) -> Result<(), Error> {
url.set_path("/admin/v0/upload");
let req = httpc
.post(url)
.header(header::ACCEPT, "text/plain")
+ .query(&[("id", pkg_id)])
.basic_auth(user, Some(pass))
.body(body)
.build()?;
@@ -198,6 +200,7 @@ pub async fn publish(
registry.clone(),
&user,
&pass,
+ &pkg.id,
Body::wrap_stream(file_stream),
)
.await?;
diff --git a/image-recipe/build.sh b/image-recipe/build.sh
index 7aae39c7f..ee1f3fdfa 100755
--- a/image-recipe/build.sh
+++ b/image-recipe/build.sh
@@ -169,8 +169,16 @@ echo "deb [arch=${IB_TARGET_ARCH} signed-by=/etc/apt/trusted.gpg.d/tor.key.gpg]
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o config/archives/docker.key
echo "deb [arch=${IB_TARGET_ARCH} signed-by=/etc/apt/trusted.gpg.d/docker.key.gpg] https://download.docker.com/linux/debian ${IB_SUITE} stable" > config/archives/docker.list
-curl -fsSL https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/unstable/Debian_Testing/Release.key | gpg --dearmor -o config/archives/podman.key
-echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/trusted.gpg.d/podman.key.gpg] https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/unstable/Debian_Testing/ /" > config/archives/podman.list
+echo "deb http://deb.debian.org/debian/ trixie main contrib" > config/archives/trixie.list
+cat > config/archives/trixie.pref <<- EOF
+Package: *
+Pin: release n=trixie
+Pin-Priority: 100
+
+Package: podman
+Pin: release n=trixie
+Pin-Priority: 600
+EOF
# Dependencies
diff --git a/web/projects/marketplace/package.json b/web/projects/marketplace/package.json
index a9202baab..93b55aac3 100644
--- a/web/projects/marketplace/package.json
+++ b/web/projects/marketplace/package.json
@@ -1,6 +1,6 @@
{
"name": "@start9labs/marketplace",
- "version": "0.3.25",
+ "version": "0.3.28",
"peerDependencies": {
"@angular/common": ">=13.2.0",
"@angular/core": ">=13.2.0",
diff --git a/web/projects/marketplace/src/components/menu/menu.component.scss b/web/projects/marketplace/src/components/menu/menu.component.scss
index 3730d65df..49f6427ee 100644
--- a/web/projects/marketplace/src/components/menu/menu.component.scss
+++ b/web/projects/marketplace/src/components/menu/menu.component.scss
@@ -33,6 +33,7 @@ header {
1;
border-width: 0;
border-right: 0.125rem solid;
+ overflow: visible;
}
@media screen and (min-width: 1536px) {
diff --git a/web/projects/marketplace/src/components/menu/menu.component.ts b/web/projects/marketplace/src/components/menu/menu.component.ts
index 40ae53743..db486eae5 100644
--- a/web/projects/marketplace/src/components/menu/menu.component.ts
+++ b/web/projects/marketplace/src/components/menu/menu.component.ts
@@ -66,6 +66,7 @@ export class MenuComponent implements OnDestroy {
this.query = ''
this.categoryService.resetQuery()
this.categoryService.changeCategory(category)
+ this.categoryService.handleNavigation()
}
onQueryChange(query: string): void {
diff --git a/web/projects/marketplace/src/pages/list/categories/categories.component.scss b/web/projects/marketplace/src/pages/list/categories/categories.component.scss
index 94868e02e..b0cf559f8 100644
--- a/web/projects/marketplace/src/pages/list/categories/categories.component.scss
+++ b/web/projects/marketplace/src/pages/list/categories/categories.component.scss
@@ -32,7 +32,7 @@ button {
}
@media (min-width: 640px) {
- width: 120%;
+ width: 108%;
padding: 0.5rem;
margin-bottom: 0.75rem;
diff --git a/web/projects/marketplace/src/pages/list/categories/categories.component.ts b/web/projects/marketplace/src/pages/list/categories/categories.component.ts
index f9778a8f3..d59032378 100644
--- a/web/projects/marketplace/src/pages/list/categories/categories.component.ts
+++ b/web/projects/marketplace/src/pages/list/categories/categories.component.ts
@@ -32,7 +32,7 @@ export class CategoriesComponent {
case 'all':
return 'tuiIconGridLarge'
case 'bitcoin':
- return 'tuiIconBitcoin'
+ return 'assets/img/icons/logo-bitcoin.svg'
case 'messaging':
case 'communications':
return 'tuiIconMessageCircleLarge'
diff --git a/web/projects/marketplace/src/pages/list/item/item.component.html b/web/projects/marketplace/src/pages/list/item/item.component.html
index eb88b4ee8..60f7bbf3a 100644
--- a/web/projects/marketplace/src/pages/list/item/item.component.html
+++ b/web/projects/marketplace/src/pages/list/item/item.component.html
@@ -1,14 +1,20 @@
-
+
-
![{{ pkg.manifest.title }} Icon]()
+
diff --git a/web/projects/marketplace/src/pages/list/item/item.component.scss b/web/projects/marketplace/src/pages/list/item/item.component.scss
index 8c9c137c6..11a1b7108 100644
--- a/web/projects/marketplace/src/pages/list/item/item.component.scss
+++ b/web/projects/marketplace/src/pages/list/item/item.component.scss
@@ -1,10 +1,7 @@
.item-container {
- height: 100%;
position: relative;
- min-width: 300px;
border-radius: 1.5rem;
padding: 5rem 2rem 2rem 2.5rem;
- gap: 1rem;
transition-property: transform;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 500ms;
@@ -56,21 +53,20 @@
top: -2.5rem;
border-radius: 9999px;
object-fit: cover;
- backdrop-filter: blur(24px);
- background-color: rgb(0 0 0 / 0.5);
transform: none;
}
.detail {
margin-top: 0.75rem;
mix-blend-mode: plus-lighter;
-
+
&-title {
display: inline-block;
font-size: 1.5rem;
line-height: 2rem;
margin-bottom: 0.25rem;
font-weight: 400;
+ will-change: transform, text-indent;
}
&-description {
diff --git a/web/projects/marketplace/src/pages/list/item/item.component.ts b/web/projects/marketplace/src/pages/list/item/item.component.ts
index e1e4544b0..b608417ab 100644
--- a/web/projects/marketplace/src/pages/list/item/item.component.ts
+++ b/web/projects/marketplace/src/pages/list/item/item.component.ts
@@ -1,5 +1,11 @@
-import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
-import { MarketplacePkg } from '../../../types'
+import {
+ ChangeDetectionStrategy,
+ Component,
+ Input,
+ inject,
+} from '@angular/core'
+import { MarketplacePkg, StoreIdentity } from '../../../types'
+import { AbstractMarketplaceService } from '../../../services/marketplace.service'
@Component({
selector: 'marketplace-item',
@@ -10,4 +16,16 @@ import { MarketplacePkg } from '../../../types'
export class ItemComponent {
@Input({ required: true })
pkg!: MarketplacePkg
+
+ private readonly marketplaceService = inject(AbstractMarketplaceService)
+ readonly marketplace$ = this.marketplaceService.getSelectedHost$()
+
+ determineIcon(marketplace: StoreIdentity | null): string {
+ try {
+ const iconUrl = new URL(this.pkg.icon)
+ return iconUrl.href
+ } catch (e) {
+ return `${marketplace?.url}package/v0/icon/${this.pkg.manifest.id}`
+ }
+ }
}
diff --git a/web/projects/marketplace/src/pages/list/item/item.module.ts b/web/projects/marketplace/src/pages/list/item/item.module.ts
index 1a11b7f77..329ac0b4d 100644
--- a/web/projects/marketplace/src/pages/list/item/item.module.ts
+++ b/web/projects/marketplace/src/pages/list/item/item.module.ts
@@ -3,10 +3,17 @@ import { NgModule } from '@angular/core'
import { RouterModule } from '@angular/router'
import { SharedPipesModule, TickerModule } from '@start9labs/shared'
import { ItemComponent } from './item.component'
+import { TuiLetModule } from '@taiga-ui/cdk'
@NgModule({
declarations: [ItemComponent],
exports: [ItemComponent],
- imports: [CommonModule, RouterModule, SharedPipesModule, TickerModule],
+ imports: [
+ CommonModule,
+ RouterModule,
+ SharedPipesModule,
+ TickerModule,
+ TuiLetModule,
+ ],
})
export class ItemModule {}
diff --git a/web/projects/marketplace/src/pages/release-notes/release-notes.component.ts b/web/projects/marketplace/src/pages/release-notes/release-notes.component.ts
index a5262b404..8268cf541 100644
--- a/web/projects/marketplace/src/pages/release-notes/release-notes.component.ts
+++ b/web/projects/marketplace/src/pages/release-notes/release-notes.component.ts
@@ -39,7 +39,21 @@ export class ReleaseNotesComponent {
}
asIsOrder(a: KeyValue
, b: KeyValue) {
- return a.key > b.key ? -1 : b.key > a.key ? 1 : 0
+ const a1 = a.key.split('.')
+ const b1 = b.key.split('.')
+ // contingency in case there's a 4th or 5th version
+ const len = Math.min(a1.length, b1.length)
+ // look through each version number and compare.
+ for (let i = 0; i < len; i++) {
+ const a2 = +a1[i] || 0
+ const b2 = +b1[i] || 0
+
+ if (a2 !== b2) {
+ // sort descending
+ return a2 > b2 ? -1 : 1
+ }
+ }
+ return a1.length - b1.length
}
async showReleaseNotes(content: PolymorpheusContent) {
diff --git a/web/projects/marketplace/src/pages/show/dependencies/dependency-item.component.ts b/web/projects/marketplace/src/pages/show/dependencies/dependency-item.component.ts
index 6e338a13d..e37e31cc8 100644
--- a/web/projects/marketplace/src/pages/show/dependencies/dependency-item.component.ts
+++ b/web/projects/marketplace/src/pages/show/dependencies/dependency-item.component.ts
@@ -1,19 +1,26 @@
import { CommonModule, KeyValue } from '@angular/common'
-import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
+import {
+ ChangeDetectionStrategy,
+ Component,
+ Input,
+ inject,
+} from '@angular/core'
import { EmverPipesModule } from '@start9labs/shared'
-import { Dependency, MarketplacePkg } from '../../../types'
+import { Dependency, MarketplacePkg, StoreIdentity } from '../../../types'
import { RouterModule } from '@angular/router'
import { TuiAvatarModule, TuiLineClampModule } from '@taiga-ui/kit'
+import { TuiLetModule } from '@taiga-ui/cdk'
+import { AbstractMarketplaceService } from '../../../services/marketplace.service'
@Component({
selector: 'marketplace-dep-item',
template: `
-
+
- getImage(key: string): string {
+ private readonly marketplaceService = inject(AbstractMarketplaceService)
+ readonly marketplace$ = this.marketplaceService.getSelectedHost$()
+
+ getImage(key: string, marketplace: StoreIdentity | null) {
const icon = this.pkg.dependencyMetadata[key]?.icon
- // @TODO fix when registry api is updated to include mimetype in icon url
- // return icon ? `data:image/png;base64,${icon}` : key.substring(0, 2)
- return icon ? icon : key.substring(0, 2)
+ const camelToSnakeCase = (str: string) =>
+ str.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`)
+
+ if (icon) {
+ try {
+ const iconUrl = new URL(icon)
+ return iconUrl.href
+ } catch (e) {
+ return `${marketplace?.url}package/v0/icon/${camelToSnakeCase(key)}`
+ }
+ } else {
+ return key.substring(0, 2)
+ }
}
getTitle(key: string): string {
diff --git a/web/projects/marketplace/src/pages/show/hero/hero.component.ts b/web/projects/marketplace/src/pages/show/hero/hero.component.ts
index b6d1648d2..7a26cc51f 100644
--- a/web/projects/marketplace/src/pages/show/hero/hero.component.ts
+++ b/web/projects/marketplace/src/pages/show/hero/hero.component.ts
@@ -1,23 +1,29 @@
import { CommonModule } from '@angular/common'
-import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
+import {
+ ChangeDetectionStrategy,
+ Component,
+ Input,
+ inject,
+} from '@angular/core'
import { SharedPipesModule, TickerModule } from '@start9labs/shared'
-import { MarketplacePkg } from '../../../types'
+import { MarketplacePkg, StoreIdentity } from '../../../types'
+import { TuiLetModule } from '@taiga-ui/cdk'
+import { AbstractMarketplaceService } from '../../../services/marketplace.service'
@Component({
selector: 'marketplace-package-hero',
template: `
-
+
@@ -61,11 +67,11 @@ import { MarketplacePkg } from '../../../types'
padding: 4rem 2rem 0 2rem;
@media (min-width: 376px) {
- min-height: 26vh;
+ min-height: 20vh;
}
@media (min-width: 768px) {
- min-height: 14rem;
+ min-height: 11rem;
}
img {
@@ -75,8 +81,6 @@ import { MarketplacePkg } from '../../../types'
border-radius: 9999px;
object-fit: cover;
position: absolute;
- backdrop-filter: blur(24px);
- background-color: rgb(0 0 0 / 0.5);
top: -2.25rem;
left: 1.75rem;
z-index: 1;
@@ -157,9 +161,21 @@ import { MarketplacePkg } from '../../../types'
],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
- imports: [CommonModule, SharedPipesModule, TickerModule],
+ imports: [CommonModule, SharedPipesModule, TickerModule, TuiLetModule],
})
export class MarketplacePackageHeroComponent {
@Input({ required: true })
pkg!: MarketplacePkg
+
+ private readonly marketplaceService = inject(AbstractMarketplaceService)
+ readonly marketplace$ = this.marketplaceService.getSelectedHost$()
+
+ determineIcon(marketplace: StoreIdentity | null) {
+ try {
+ const iconUrl = new URL(this.pkg.icon)
+ return iconUrl.href
+ } catch (e) {
+ return `${marketplace?.url}package/v0/icon/${this.pkg.manifest.id}`
+ }
+ }
}
diff --git a/web/projects/marketplace/src/services/category.service.ts b/web/projects/marketplace/src/services/category.service.ts
index f9b0bc1e2..e044dddc1 100644
--- a/web/projects/marketplace/src/services/category.service.ts
+++ b/web/projects/marketplace/src/services/category.service.ts
@@ -13,4 +13,6 @@ export abstract class AbstractCategoryService {
abstract getQuery$(): Observable
abstract resetQuery(): void
+
+ abstract handleNavigation(): void
}
diff --git a/web/projects/shared/assets/img/icons/logo-bitcoin.svg b/web/projects/shared/assets/img/icons/logo-bitcoin.svg
new file mode 100644
index 000000000..89958abb0
--- /dev/null
+++ b/web/projects/shared/assets/img/icons/logo-bitcoin.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/web/projects/shared/package.json b/web/projects/shared/package.json
index c229bacb3..272da854d 100644
--- a/web/projects/shared/package.json
+++ b/web/projects/shared/package.json
@@ -1,6 +1,6 @@
{
"name": "@start9labs/shared",
- "version": "0.3.12",
+ "version": "0.3.13",
"peerDependencies": {
"@angular/common": "^17.0.6",
"@angular/core": "^17.0.6",
diff --git a/web/projects/ui/src/app/routes/portal/routes/service/components/backups.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/components/backups.component.ts
index 799556ffd..48a3aea47 100644
--- a/web/projects/ui/src/app/routes/portal/routes/service/components/backups.component.ts
+++ b/web/projects/ui/src/app/routes/portal/routes/service/components/backups.component.ts
@@ -59,7 +59,9 @@ export class ServiceBackupsComponent {
)
readonly next = computed(() =>
- daysBetween(new Date(), new Date(this.pkg().nextBackup || new Date())),
+ // TODO @lucy add this back in when types fixed for PackageDataEntry ie. when next/minor merge resolved
+ // daysBetween(new Date(), new Date(this.pkg().nextBackup || new Date())),
+ daysBetween(new Date(), new Date(new Date())),
)
readonly ago = {
diff --git a/web/projects/ui/src/app/routes/portal/routes/system/marketplace/marketplace.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/marketplace/marketplace.component.ts
index 91399ccd9..6ec21b98b 100644
--- a/web/projects/ui/src/app/routes/portal/routes/system/marketplace/marketplace.component.ts
+++ b/web/projects/ui/src/app/routes/portal/routes/system/marketplace/marketplace.component.ts
@@ -101,8 +101,7 @@ import { TuiScrollbarModule } from '@taiga-ui/core'
&-list {
display: grid;
grid-template-columns: repeat(1, minmax(0, 1fr));
- gap: 4rem;
- list-style-type: none;
+ gap: 4rem 3rem;
padding: 1.5rem;
@media (min-width: 768px) {
@@ -111,9 +110,12 @@ import { TuiScrollbarModule } from '@taiga-ui/core'
@media (min-width: 1024px) {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
- @media (min-width: 1536px) {
+ @media (min-width: 1280px) {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
+ @media (min-width: 1536px) {
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ }
.tile-wrapper {
display: block;
diff --git a/web/projects/ui/src/app/routes/portal/routes/system/marketplace/modals/preview.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/marketplace/modals/preview.component.ts
index 3a0d98ad0..deb5d990f 100644
--- a/web/projects/ui/src/app/routes/portal/routes/system/marketplace/modals/preview.component.ts
+++ b/web/projects/ui/src/app/routes/portal/routes/system/marketplace/modals/preview.component.ts
@@ -152,6 +152,10 @@ import { Router } from '@angular/router'
height: 100%;
place-self: center;
}
+
+ marketplace-additional {
+ padding-bottom: 2rem;
+ }
`,
],
standalone: true,
diff --git a/web/projects/ui/src/app/services/category.service.ts b/web/projects/ui/src/app/services/category.service.ts
index 045fdae86..79243d757 100644
--- a/web/projects/ui/src/app/services/category.service.ts
+++ b/web/projects/ui/src/app/services/category.service.ts
@@ -1,9 +1,12 @@
-import { Injectable } from '@angular/core'
+import { Injectable, inject } from '@angular/core'
import { Observable } from 'rxjs'
import { AbstractCategoryService } from '@start9labs/marketplace'
+import { Router } from '@angular/router'
@Injectable()
export class CategoryService extends AbstractCategoryService {
+ private readonly router = inject(Router)
+
getCategory$(): Observable {
return this.category$
}
@@ -23,4 +26,8 @@ export class CategoryService extends AbstractCategoryService {
resetQuery() {
this.query$.next('')
}
+
+ handleNavigation() {
+ this.router.navigate([])
+ }
}
diff --git a/web/tslint.json b/web/tslint.json
index f50f6cab3..ad6856103 100644
--- a/web/tslint.json
+++ b/web/tslint.json
@@ -1,6 +1,5 @@
{
"rules": {
- "no-unused-variable": true,
"no-unused-expression": true,
"semicolon": [true, "never"],
"no-trailing-whitespace": true,