Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions projects/start-os/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ monorepo-wide rules and [ARCHITECTURE.md](ARCHITECTURE.md) for how this product
- **Ask before destructive `make` recipes** — `update*`, `reflash`, `wormhole*`,
image flashing, and `make clean*` consume hours/disk and may touch a live
device.
- **The UI's `manifest.webmanifest` is rewritten before it is served** —
`RpcContext`'s router replaces `name` and `short_name` with the server's
hostname. Keep it out of `ngsw-config.json`: an asset group makes the service
worker hash-check it against a body it never receives and drop out of service.
- **The `beta` feature swaps the UI seed** (`patchdb-ui-seed.beta.json`) and
forwards to `start-core`'s `beta` feature — keep both seeds in sync when you
change seed shape.
Expand Down
6 changes: 6 additions & 0 deletions projects/start-os/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ file tracks notable changes since the move to the monorepo.
optional name and hostname. Setting it moves the `.local` address, where
passing only a name used to change the label alone.

- **Installing the StartOS UI as an app names it after your server.** Adding it
to a phone's home screen or installing it from a desktop browser labels it
with the server name in place of `StartOS`. An app installed before this
update, or before a rename, may keep the name it was installed with — remove
it and install it again to pick up the current one.

- **The NVIDIA images now use NVIDIA's open kernel modules, which support GeForce
RTX 20-series, Quadro RTX and newer.** This is what makes current cards work at
all — an RTX 50-series, an RTX PRO 6000 or an NVIDIA GB10 can only be driven by
Expand Down
6 changes: 4 additions & 2 deletions projects/start-os/docs/src/server-name.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Server Name

Your server name is its [mDNS address](mdns.md) without the `.local` on the end. A server named `my-cool-server` answers to `my-cool-server.local` on your local network, and shows `my-cool-server` in your browser tab. It is also the name the server calls itself over SSH and in its own logs.
Your server name is its [mDNS address](mdns.md) without the `.local` on the end. A server named `my-cool-server` answers to `my-cool-server.local` on your local network, and every installed service is reachable at that address too. The name also shows in your browser tab, names the app if you install the UI to a home screen or desktop, and is what the server calls itself over SSH and in its own logs.

You choose the name during [initial setup](initial-setup.md), and you can change it at any time under `System > General Settings > Server Name`. A name may contain lowercase letters, numbers, and hyphens, and may be up to 32 characters long. It cannot start or end with a hyphen.

> [!WARNING]
> If you are currently connected via your mDNS address, changing the server name will require you to switch to the new address. You will be prompted with the new address after saving.
> If you are currently connected via your mDNS address, changing the server name will require you to switch to the new address. You will be prompted with the new address after saving. Installed services move to the new address once the server restarts.

An app you have already installed may keep the name it was installed with. Remove it and install it again to pick up a new one.
8 changes: 1 addition & 7 deletions projects/start-os/web/ui/ngsw-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,7 @@
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/manifest.webmanifest",
"/*.css",
"/*.js"
]
"files": ["/favicon.ico", "/index.html", "/*.css", "/*.js"]
}
},
{
Expand Down
13 changes: 13 additions & 0 deletions projects/start-os/web/ui/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Component, inject } from '@angular/core'
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
import { Meta } from '@angular/platform-browser'
import { RouterOutlet } from '@angular/router'
import { i18nService } from '@start9labs/shared'
import { TuiRoot } from '@taiga-ui/core'
Expand Down Expand Up @@ -33,6 +34,7 @@ import { PatchMonitorService } from './services/patch-monitor.service'
})
export class AppComponent {
private readonly i18n = inject(i18nService)
private readonly meta = inject(Meta)

readonly subscription = merge(
inject(PatchDataService),
Expand All @@ -47,4 +49,15 @@ export class AppComponent {
.subscribe(language => {
this.i18n.setLangLocal(language || 'en_US')
})

// Safari takes the Home Screen name from this meta tag, not the manifest.
readonly appName = inject<PatchDB<DataModel>>(PatchDB)
.watch$('serverInfo', 'hostname')
.pipe(takeUntilDestroyed())
.subscribe(hostname => {
this.meta.updateTag({
name: 'apple-mobile-web-app-title',
content: hostname || 'StartOS',
})
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ import { map } from 'rxjs'
@if (form.valid) {
<p class="hostname-preview">{{ hostname() }}.local</p>
}
<div class="used-for">
<p>{{ 'Used for:' | i18n }}</p>
<ul>
<li>{{ 'The address of your server' | i18n }}</li>
<li>{{ 'The local address of every installed service' | i18n }}</li>
<li>
{{ 'The browser tab and the name of the installed app' | i18n }}
</li>
<li>{{ 'SSH and system logs' | i18n }}</li>
</ul>
</div>
<footer>
<button
tuiButton
Expand All @@ -55,6 +66,21 @@ import { map } from 'rxjs'
margin-top: 0.25rem;
}

.used-for {
color: var(--tui-text-secondary);
font: var(--tui-typography-body-s);
margin-top: 1rem;

p {
margin: 0;
}

ul {
margin: 0.25rem 0 0;
padding-inline-start: 1.25rem;
}
}

footer {
display: flex;
gap: 1rem;
Expand Down
34 changes: 34 additions & 0 deletions shared-libs/crates/start-core/src/net/static_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,16 @@ impl UiContext for RpcContext {
}
})
})
.route("/manifest.webmanifest", {
let ctx = self.clone();
get(move || {
let ctx = ctx.clone();
async move {
ctx.account
.peek(|account| webmanifest_send(Self::ui_dir(), &account.hostname))
}
})
})
.route(
"/static/local-root-ca.mobileconfig",
get(move || {
Expand Down Expand Up @@ -457,6 +467,30 @@ pub fn bad_request() -> Response {
.unwrap()
}

fn webmanifest_send(
ui_dir: &'static Dir<'static>,
hostname: &ServerHostname,
) -> Result<Response, Error> {
let mut manifest: serde_json::Map<String, serde_json::Value> = serde_json::from_slice(
ui_dir
.get_file("manifest.webmanifest")
.or_not_found("manifest.webmanifest")?
.contents(),
)
.with_kind(ErrorKind::Deserialization)?;
manifest.insert("name".into(), hostname.as_ref().into());
manifest.insert("short_name".into(), hostname.as_ref().into());
let body = serde_json::to_vec(&manifest).with_kind(ErrorKind::Serialization)?;

Response::builder()
.status(StatusCode::OK)
.header(CONTENT_TYPE, "application/manifest+json")
.header(CACHE_CONTROL, "no-cache")
.header(CONTENT_LENGTH, body.len())
.body(Body::from(body))
.with_kind(ErrorKind::Network)
}

fn cert_send(cert: &X509, hostname: &ServerHostname) -> Result<Response, Error> {
let pem = cert.to_pem()?;
Response::builder()
Expand Down
5 changes: 5 additions & 0 deletions shared-libs/ts-modules/shared/src/i18n/dictionaries/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -811,4 +811,9 @@ export default {
918: 'Darf höchstens 32 Zeichen lang sein',
919: 'Nur Kleinbuchstaben, Ziffern und Bindestriche',
920: 'Darf nicht mit einem Bindestrich beginnen oder enden',
921: 'Verwendet für:',
922: 'Die Adresse Ihres Servers',
923: 'Die lokale Adresse jedes installierten Dienstes',
924: 'Der Browser-Tab und der Name der installierten App',
925: 'SSH und Systemprotokolle',
} satisfies i18n
5 changes: 5 additions & 0 deletions shared-libs/ts-modules/shared/src/i18n/dictionaries/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -812,4 +812,9 @@ export const ENGLISH: Record<string, number> = {
'Must be 32 characters or less': 918,
'Lowercase letters, numbers, and hyphens only': 919,
'Cannot start or end with a hyphen': 920,
'Used for:': 921,
'The address of your server': 922,
'The local address of every installed service': 923,
'The browser tab and the name of the installed app': 924,
'SSH and system logs': 925,
}
5 changes: 5 additions & 0 deletions shared-libs/ts-modules/shared/src/i18n/dictionaries/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -811,4 +811,9 @@ export default {
918: 'Debe tener 32 caracteres o menos',
919: 'Solo letras minúsculas, números y guiones',
920: 'No puede empezar ni terminar con un guion',
921: 'Se usa para:',
922: 'La dirección de su servidor',
923: 'La dirección local de cada servicio instalado',
924: 'La pestaña del navegador y el nombre de la aplicación instalada',
925: 'SSH y los registros del sistema',
} satisfies i18n
5 changes: 5 additions & 0 deletions shared-libs/ts-modules/shared/src/i18n/dictionaries/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -811,4 +811,9 @@ export default {
918: 'Doit comporter au maximum 32 caractères',
919: 'Uniquement des lettres minuscules, des chiffres et des traits d’union',
920: 'Ne peut pas commencer ni finir par un trait d’union',
921: 'Utilisé pour :',
922: 'L’adresse de votre serveur',
923: 'L’adresse locale de chaque service installé',
924: 'L’onglet du navigateur et le nom de l’application installée',
925: 'SSH et les journaux système',
} satisfies i18n
5 changes: 5 additions & 0 deletions shared-libs/ts-modules/shared/src/i18n/dictionaries/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -811,4 +811,9 @@ export default {
918: 'Może mieć maksymalnie 32 znaki',
919: 'Tylko małe litery, cyfry i myślniki',
920: 'Nie może zaczynać się ani kończyć myślnikiem',
921: 'Używana do:',
922: 'Adres Twojego serwera',
923: 'Lokalny adres każdej zainstalowanej usługi',
924: 'Karta przeglądarki i nazwa zainstalowanej aplikacji',
925: 'SSH i dzienniki systemowe',
} satisfies i18n