Initial commit: Premier Gunner tracker + StartOS 0.4.0 s9pk package
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
.git
|
||||
.gitmodules
|
||||
node_modules
|
||||
startos
|
||||
javascript
|
||||
ncc-cache
|
||||
assets
|
||||
docker-images
|
||||
*.s9pk
|
||||
app/node_modules
|
||||
app/data
|
||||
@@ -0,0 +1,9 @@
|
||||
*.s9pk
|
||||
startos/*.js
|
||||
node_modules/
|
||||
.DS_Store
|
||||
.vscode/
|
||||
docker-images
|
||||
javascript
|
||||
ncc-cache
|
||||
app/
|
||||
@@ -0,0 +1,5 @@
|
||||
# AGENTS.md
|
||||
|
||||
You are working in a StartOS service-package repository — a repo that builds a `.s9pk` for installation on StartOS.
|
||||
|
||||
**Before doing anything in this repo, read [CONTRIBUTING.md](./CONTRIBUTING.md) and every document it links to.** That covers what this package is, how it's built, how it ships, and the conventions to follow. Do not begin work until you have read them all.
|
||||
@@ -0,0 +1 @@
|
||||
@AGENTS.md
|
||||
@@ -0,0 +1,43 @@
|
||||
# Contributing
|
||||
|
||||
## Keep these in sync
|
||||
|
||||
- **[`README.md`](./README.md)** — what this package is and how it's built (image, volumes, interfaces). Technical reference for developers and AI assistants.
|
||||
- **[`instructions.md`](./instructions.md)** — the user-facing instructions packed into the `.s9pk` and shown on the **Instructions** tab in StartOS, for the person running the service.
|
||||
- **[`TODO.md`](./TODO.md)** — pending work on this package.
|
||||
|
||||
**Read all three before starting any work.** Any code change that affects user-visible behavior must update `README.md` and `instructions.md` in the same change; add to `TODO.md` when you defer work, and remove items when complete. Content rules: [Writing READMEs](https://docs.start9.com/packaging/writing-readmes.html), [Writing Instructions](https://docs.start9.com/packaging/writing-instructions.html).
|
||||
|
||||
## Environment setup
|
||||
|
||||
See [Environment Setup](https://docs.start9.com/packaging/environment-setup.html)
|
||||
|
||||
## Building
|
||||
|
||||
```bash
|
||||
npm ci # install dependencies
|
||||
make # build the universal .s9pk
|
||||
```
|
||||
|
||||
For a complete list of build options, see [Makefile](https://docs.start9.com/packaging/makefile.html).
|
||||
|
||||
## Updating the upstream version
|
||||
|
||||
1. Apply the upstream bump per [UPDATING.md](./UPDATING.md).
|
||||
2. Update `version` and `releaseNotes` in `startos/versions/current.ts` — the latest version always lives in that file, so an in-place edit is all most bumps need. A new file is spun off only when the bump requires a migration — see [Versions](https://docs.start9.com/packaging/versions.html).
|
||||
|
||||
## CI/CD
|
||||
|
||||
Three workflows under `.github/workflows/` wrap reusable workflows in [`start9labs/shared-workflows`](https://github.com/Start9Labs/shared-workflows):
|
||||
|
||||
- **`build.yml`** — on PR, builds the `.s9pk` and uploads per-arch artifacts for sideload testing.
|
||||
- **`release.yml`** — on `v*` tag, builds per arch and publishes to the test registry.
|
||||
- **`tagAndRelease.yml`** — on push to `master`, tags `v<version>` and runs `release.yml`, skipping if already in production.
|
||||
|
||||
Promotion to `beta` and `prod` is a separate, manual step.
|
||||
|
||||
## How to contribute
|
||||
|
||||
1. Fork the repository and create a branch from `master`.
|
||||
2. Make your changes — including the doc updates above.
|
||||
3. Open a pull request to `master`.
|
||||
@@ -0,0 +1,17 @@
|
||||
# Build stage: compile native deps (better-sqlite3) with toolchain present.
|
||||
FROM node:22-bookworm-slim AS build
|
||||
WORKDIR /app
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends python3 make g++ \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
COPY app/package.json app/package-lock.json ./
|
||||
RUN npm ci --omit=dev
|
||||
COPY app/ ./
|
||||
|
||||
# Runtime stage: slim image with prebuilt node_modules copied in.
|
||||
FROM node:22-bookworm-slim
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
COPY --from=build /app ./
|
||||
EXPOSE 3000
|
||||
CMD ["node", "src/server.js"]
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Start9 Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,39 @@
|
||||
ARCHES := x86 arm
|
||||
# overrides to s9pk.mk must precede the include statement
|
||||
|
||||
# Build x86_64 + aarch64 only (matches manifest images.arch).
|
||||
# The default goal vendors the Node app into ./app (the Docker build context)
|
||||
# before delegating to s9pk.mk's build matrix.
|
||||
.DEFAULT_GOAL := default
|
||||
.PHONY: default prep clean-app
|
||||
|
||||
# Root of the Premier Gunner Node app (this s9pk lives in ./s9pk).
|
||||
APP_SRC := ..
|
||||
|
||||
default: prep
|
||||
@$(MAKE) --no-print-directory all
|
||||
|
||||
# Sync app sources into ./app so the Dockerfile can COPY them. node_modules and
|
||||
# runtime data are excluded; the Dockerfile reinstalls deps via `npm ci`.
|
||||
prep:
|
||||
@echo " Vendoring Node app into ./app ..."
|
||||
@rm -rf app
|
||||
@mkdir -p app
|
||||
@rsync -a \
|
||||
--exclude='node_modules/' \
|
||||
--exclude='data/' \
|
||||
--exclude='s9pk/' \
|
||||
--exclude='.git/' \
|
||||
--exclude='*.log' \
|
||||
--exclude='.DS_Store' \
|
||||
--exclude='.env' \
|
||||
"$(APP_SRC)/package.json" \
|
||||
"$(APP_SRC)/package-lock.json" \
|
||||
"$(APP_SRC)/src" \
|
||||
"$(APP_SRC)/public" \
|
||||
app/
|
||||
|
||||
clean-app:
|
||||
rm -rf app
|
||||
|
||||
include s9pk.mk
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
<p align="center">
|
||||
<img src="icon.svg" alt="Hello World Logo" width="21%">
|
||||
</p>
|
||||
|
||||
# Hello World on StartOS
|
||||
|
||||
> **Upstream repo:** <https://github.com/Start9Labs/hello-world>
|
||||
|
||||
A minimal reference service for StartOS. It displays a simple web page — nothing more. Use [this repository](https://github.com/Start9Labs/hello-world-startos) as a template when packaging a new service for StartOS.
|
||||
|
||||
## Getting Started
|
||||
|
||||
To learn how to use this template to create your own StartOS service package, see the [Packaging Guide](https://docs.start9.com/packaging).
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Image and Container Runtime](#image-and-container-runtime)
|
||||
- [Volume and Data Layout](#volume-and-data-layout)
|
||||
- [Installation and First-Run Flow](#installation-and-first-run-flow)
|
||||
- [Configuration Management](#configuration-management)
|
||||
- [Network Access and Interfaces](#network-access-and-interfaces)
|
||||
- [Actions (StartOS UI)](#actions-startos-ui)
|
||||
- [Backups and Restore](#backups-and-restore)
|
||||
- [Health Checks](#health-checks)
|
||||
- [Dependencies](#dependencies)
|
||||
- [Limitations and Differences](#limitations-and-differences)
|
||||
- [What Is Unchanged from Upstream](#what-is-unchanged-from-upstream)
|
||||
- [Contributing](#contributing)
|
||||
- [Quick Reference for AI Consumers](#quick-reference-for-ai-consumers)
|
||||
|
||||
---
|
||||
|
||||
## Image and Container Runtime
|
||||
|
||||
| Property | Value |
|
||||
| ------------- | -------------------------------------- |
|
||||
| Image | `ghcr.io/start9labs/hello-world` |
|
||||
| Architectures | x86_64, aarch64, riscv64 |
|
||||
| Command | `hello-world` |
|
||||
|
||||
---
|
||||
|
||||
## Volume and Data Layout
|
||||
|
||||
| Volume | Mount Point | Purpose |
|
||||
| ------ | ----------- | --------------- |
|
||||
| `main` | `/data` | Persistent data |
|
||||
|
||||
---
|
||||
|
||||
## Installation and First-Run Flow
|
||||
|
||||
No special setup. Install and start — the web page is immediately available.
|
||||
|
||||
---
|
||||
|
||||
## Configuration Management
|
||||
|
||||
No configurable settings. The service runs with no user-facing configuration.
|
||||
|
||||
---
|
||||
|
||||
## Network Access and Interfaces
|
||||
|
||||
| Interface | Port | Protocol | Purpose |
|
||||
| --------- | ---- | -------- | -------------------- |
|
||||
| Web UI | 80 | HTTP | Hello World web page |
|
||||
|
||||
**Access methods:**
|
||||
|
||||
- LAN IP with unique port
|
||||
- `<hostname>.local` with unique port
|
||||
- Tor `.onion` address
|
||||
- Custom domains (if configured)
|
||||
|
||||
---
|
||||
|
||||
## Actions (StartOS UI)
|
||||
|
||||
None.
|
||||
|
||||
---
|
||||
|
||||
## Backups and Restore
|
||||
|
||||
**Included in backup:**
|
||||
|
||||
- `main` volume
|
||||
|
||||
**Restore behavior:** Volume is fully restored before the service starts.
|
||||
|
||||
---
|
||||
|
||||
## Health Checks
|
||||
|
||||
| Check | Method | Messages |
|
||||
| ------------- | ------------------- | ------------------------------------------------------------------ |
|
||||
| Web Interface | Port listening (80) | Success: "The web interface is ready" / Error: "The web interface is not ready" |
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
None.
|
||||
|
||||
---
|
||||
|
||||
## Limitations and Differences
|
||||
|
||||
1. **No meaningful functionality** — this is a reference/template package only
|
||||
|
||||
---
|
||||
|
||||
## What Is Unchanged from Upstream
|
||||
|
||||
The service is identical to upstream. There are no modifications.
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for build instructions and development workflow.
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference for AI Consumers
|
||||
|
||||
```yaml
|
||||
package_id: hello-world
|
||||
image: ghcr.io/start9labs/hello-world
|
||||
architectures: [x86_64, aarch64, riscv64]
|
||||
volumes:
|
||||
main: /data
|
||||
ports:
|
||||
ui: 80
|
||||
dependencies: none
|
||||
startos_managed_env_vars: none
|
||||
actions: none
|
||||
```
|
||||
@@ -0,0 +1 @@
|
||||
# TODO
|
||||
@@ -0,0 +1,17 @@
|
||||
# Updating the upstream version
|
||||
|
||||
This package wraps Start9 Labs' own [hello-world](https://github.com/Start9Labs/hello-world) source, which we build and publish ourselves as `ghcr.io/start9labs/hello-world`. "Upstream" here means that source repo, not the image namespace.
|
||||
|
||||
## Determining the upstream version
|
||||
|
||||
- **hello-world** ([Start9Labs/hello-world](https://github.com/Start9Labs/hello-world)) — fetch the latest release tag:
|
||||
|
||||
```sh
|
||||
gh release view -R Start9Labs/hello-world --json tagName -q .tagName
|
||||
```
|
||||
|
||||
The current pin lives in `startos/manifest/index.ts` at `images['hello-world'].source.dockerTag` (the version after the `:` in `ghcr.io/start9labs/hello-world:<version>`).
|
||||
|
||||
## Applying the bump
|
||||
|
||||
- Bump `dockerTag` in `startos/manifest/index.ts` to `ghcr.io/start9labs/hello-world:<new version>` (drop the leading `v` from the release tag).
|
||||
@@ -0,0 +1,24 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#ff1f25"/>
|
||||
<stop offset="1" stop-color="#c50006"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="512" height="512" rx="112" fill="url(#bg)"/>
|
||||
<!-- cannon barrel -->
|
||||
<g transform="rotate(-18 256 300)">
|
||||
<rect x="150" y="232" width="230" height="58" rx="14" fill="#fff"/>
|
||||
<rect x="360" y="226" width="34" height="70" rx="8" fill="#fff"/>
|
||||
<circle cx="168" cy="296" r="34" fill="#fff"/>
|
||||
<rect x="150" y="296" width="70" height="40" rx="10" fill="#fff"/>
|
||||
</g>
|
||||
<!-- wheel -->
|
||||
<circle cx="186" cy="350" r="46" fill="#023474" stroke="#fff" stroke-width="10"/>
|
||||
<circle cx="186" cy="350" r="8" fill="#fff"/>
|
||||
<!-- soccer ball as cannonball -->
|
||||
<g transform="translate(392 196)">
|
||||
<circle r="46" fill="#fff" stroke="#15181f" stroke-width="3"/>
|
||||
<path d="M0 -22 L21 -7 L13 18 L-13 18 L-21 -7 Z" fill="#15181f"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,25 @@
|
||||
# Hello World
|
||||
|
||||
You've installed Hello World — there's nothing to configure and nothing to set up. This page covers how to open the page it serves and where to read more. (If you're a developer, Hello World is also the recommended packaging template.)
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Hello World upstream docs](https://github.com/Start9Labs/hello-world/blob/master/README.md) — the README for the web server this package runs.
|
||||
- [StartOS Packaging Guide](https://docs.start9.com/packaging) — how to build a StartOS service package from that template.
|
||||
|
||||
## What you get on StartOS
|
||||
|
||||
- **A running web server** that serves a single static page.
|
||||
- **Nothing to configure and no actions** — the service starts on its own and is immediately usable.
|
||||
|
||||
## Getting set up
|
||||
|
||||
There's no setup wizard, no admin password, no first-run prompt — Hello World is usable the moment it starts. To view the page it serves:
|
||||
|
||||
1. Open Hello World's **Dashboard** tab.
|
||||
2. Click the **Web UI** interface to open the served page in your browser.
|
||||
|
||||
## Limitations
|
||||
|
||||
- Hello World is intentionally minimal. It is not a useful service on its own; it exists to demonstrate the StartOS packaging system.
|
||||
- The page content is static and cannot be customized through the StartOS UI.
|
||||
Generated
+359
@@ -0,0 +1,359 @@
|
||||
{
|
||||
"name": "hello-world-startos",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "hello-world-startos",
|
||||
"dependencies": {
|
||||
"@start9labs/start-sdk": "1.5.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.19.0",
|
||||
"@vercel/ncc": "^0.38.4",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@iarna/toml": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-3.0.0.tgz",
|
||||
"integrity": "sha512-td6ZUkz2oS3VeleBcN+m//Q6HlCFCPrnI0FZhrt/h4XqLEdOyYp2u21nd8MdsR+WJy5r9PTDaHTDDfhf4H4l6Q==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@noble/curves": {
|
||||
"version": "1.9.7",
|
||||
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz",
|
||||
"integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "1.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.21.3 || >=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/hashes": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
|
||||
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^14.21.3 || >=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodable/entities": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz",
|
||||
"integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/nodable"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@start9labs/start-sdk": {
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-1.5.3.tgz",
|
||||
"integrity": "sha512-OyHe9J6hMvyA5ZavcLkxdVQvZcuTH9J9kagV6NDI83eAG/YpJFIq62gP/n/2PPNdHWwNSXVQmSwnsvsV8Gyg+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^3.0.0",
|
||||
"@noble/curves": "^1.9.7",
|
||||
"@noble/hashes": "^1.8.0",
|
||||
"@types/ini": "^4.1.1",
|
||||
"deep-equality-data-structures": "^2.0.0",
|
||||
"fast-xml-parser": "~5.7.0",
|
||||
"ini": "^5.0.0",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"mime": "^4.1.0",
|
||||
"yaml": "^2.8.3",
|
||||
"zod": "4.3.6",
|
||||
"zod-deep-partial": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ini": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/ini/-/ini-4.1.1.tgz",
|
||||
"integrity": "sha512-MIyNUZipBTbyUNnhvuXJTY7B6qNI78meck9Jbv3wk0OgNwRyOOVEKDutAkOs1snB/tx0FafyR6/SN4Ps0hZPeg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.19.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz",
|
||||
"integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vercel/ncc": {
|
||||
"version": "0.38.4",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.38.4.tgz",
|
||||
"integrity": "sha512-8LwjnlP39s08C08J5NstzriPvW1SP8Zfpp1BvC2sI35kPeZnHfxVkCwu4/+Wodgnd60UtT1n8K8zw+Mp7J9JmQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"ncc": "dist/ncc/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-equality-data-structures": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/deep-equality-data-structures/-/deep-equality-data-structures-2.0.0.tgz",
|
||||
"integrity": "sha512-qgrUr7MKXq7VRN+WUpQ48QlXVGL0KdibAoTX8KRg18lgOgqbEKMAW1WZsVCtakY4+XX42pbAJzTz/DlXEFM2Fg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"object-hash": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-xml-builder": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz",
|
||||
"integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-expression-matcher": "^1.5.0",
|
||||
"xml-naming": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-xml-parser": {
|
||||
"version": "5.7.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.3.tgz",
|
||||
"integrity": "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nodable/entities": "^2.1.0",
|
||||
"fast-xml-builder": "^1.1.7",
|
||||
"path-expression-matcher": "^1.5.0",
|
||||
"strnum": "^2.2.3"
|
||||
},
|
||||
"bin": {
|
||||
"fxparser": "src/cli/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/ini": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz",
|
||||
"integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "^18.17.0 || >=20.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/isomorphic-fetch": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz",
|
||||
"integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-fetch": "^2.6.1",
|
||||
"whatwg-fetch": "^3.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz",
|
||||
"integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mime": "bin/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/object-hash": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
||||
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/path-expression-matcher": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz",
|
||||
"integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz",
|
||||
"integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/strnum": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz",
|
||||
"integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/whatwg-fetch": {
|
||||
"version": "3.6.20",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz",
|
||||
"integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xml-naming": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz",
|
||||
"integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.8.3",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
|
||||
"integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/eemeli"
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
|
||||
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
},
|
||||
"node_modules/zod-deep-partial": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/zod-deep-partial/-/zod-deep-partial-1.4.4.tgz",
|
||||
"integrity": "sha512-aWkPl7hVStgE01WzbbSxCgX4O+sSpgt8JOjvFUtMTF75VgL6MhWQbiZi+AWGN85SfSTtI9gsOtL1vInoqfDVaA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"zod": "^4.1.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "hello-world-startos",
|
||||
"scripts": {
|
||||
"build": "rm -rf ./javascript && ncc build startos/index.ts -o ./javascript",
|
||||
"prettier": "prettier --write startos",
|
||||
"check": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@start9labs/start-sdk": "1.5.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.19.0",
|
||||
"@vercel/ncc": "^0.38.4",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"prettier": {
|
||||
"trailingComma": "all",
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
# ** Plumbing. DO NOT EDIT **.
|
||||
# This file is imported by ./Makefile. Make edits there
|
||||
|
||||
PACKAGE_ID := $(shell awk -F"'" '/id:/ {print $$2}' startos/manifest/index.ts)
|
||||
INGREDIENTS := $(shell start-cli s9pk list-ingredients 2>/dev/null)
|
||||
# Resolve the actual git dir so this works inside git worktrees, where .git
|
||||
# is a file pointing at <main>/.git/worktrees/<name> rather than a directory.
|
||||
GIT_DIR := $(shell git rev-parse --git-dir 2>/dev/null)
|
||||
GIT_DEPS := $(if $(GIT_DIR),$(GIT_DIR)/HEAD $(GIT_DIR)/index)
|
||||
ARCHES ?= x86 arm riscv
|
||||
# TARGETS is the list of leaf make-targets the build matrix fans out over.
|
||||
# Defaults to the arches; variant packages override (e.g. immich, ollama, vllm
|
||||
# set this to a list of variant or variant-arch leaf targets).
|
||||
TARGETS ?= $(ARCHES)
|
||||
ifdef VARIANT
|
||||
BASE_NAME := $(PACKAGE_ID)_$(VARIANT)
|
||||
else
|
||||
BASE_NAME := $(PACKAGE_ID)
|
||||
endif
|
||||
|
||||
.PHONY: all arches aarch64 x86_64 riscv64 arm arm64 x86 riscv arch/* clean install check-deps check-init package ingredients
|
||||
.DELETE_ON_ERROR:
|
||||
.SECONDARY:
|
||||
|
||||
define SUMMARY
|
||||
@manifest=$$(start-cli s9pk inspect $(1) manifest); \
|
||||
size=$$(du -h $(1) | awk '{print $$1}'); \
|
||||
title=$$(printf '%s' "$$manifest" | jq -r .title); \
|
||||
version=$$(printf '%s' "$$manifest" | jq -r .version); \
|
||||
arches=$$(printf '%s' "$$manifest" | jq -r '[.images[].arch // []] | flatten | unique | join(", ")'); \
|
||||
sdkv=$$(printf '%s' "$$manifest" | jq -r .sdkVersion); \
|
||||
gitHash=$$(printf '%s' "$$manifest" | jq -r .gitHash | sed -E 's/(.*-modified)$$/\x1b[0;31m\1\x1b[0m/'); \
|
||||
printf "\n"; \
|
||||
printf "\033[1;32m✅ Build Complete!\033[0m\n"; \
|
||||
printf "\n"; \
|
||||
printf "\033[1;37m📦 $$title\033[0m \033[36mv$$version\033[0m\n"; \
|
||||
printf "───────────────────────────────\n"; \
|
||||
printf " \033[1;36mFilename:\033[0m %s\n" "$(1)"; \
|
||||
printf " \033[1;36mSize:\033[0m %s\n" "$$size"; \
|
||||
printf " \033[1;36mArch:\033[0m %s\n" "$$arches"; \
|
||||
printf " \033[1;36mSDK:\033[0m %s\n" "$$sdkv"; \
|
||||
printf " \033[1;36mGit:\033[0m %s\n" "$$gitHash"; \
|
||||
echo ""
|
||||
endef
|
||||
|
||||
all: $(TARGETS)
|
||||
|
||||
arches: $(ARCHES)
|
||||
|
||||
# Generic make-variable introspection. Used by the release workflow to
|
||||
# read $(TARGETS) and fan out one matrix runner per target. `make -s
|
||||
# print-TARGETS` echoes the list with no other output.
|
||||
print-%:
|
||||
@echo '$($*)'
|
||||
|
||||
universal: $(BASE_NAME).s9pk
|
||||
$(call SUMMARY,$<)
|
||||
|
||||
arch/%: $(BASE_NAME)_%.s9pk
|
||||
$(call SUMMARY,$<)
|
||||
|
||||
x86 x86_64: arch/x86_64
|
||||
arm arm64 aarch64: arch/aarch64
|
||||
riscv riscv64: arch/riscv64
|
||||
|
||||
$(BASE_NAME).s9pk: $(INGREDIENTS) $(GIT_DEPS)
|
||||
@$(MAKE) --no-print-directory ingredients
|
||||
@echo " Packing '$@'..."
|
||||
start-cli s9pk pack -o $@
|
||||
|
||||
$(BASE_NAME)_%.s9pk: $(INGREDIENTS) $(GIT_DEPS)
|
||||
@$(MAKE) --no-print-directory ingredients
|
||||
@echo " Packing '$@'..."
|
||||
start-cli s9pk pack --arch=$* -o $@
|
||||
|
||||
ingredients: $(INGREDIENTS)
|
||||
@echo " Re-evaluating ingredients..."
|
||||
|
||||
install: | check-deps check-init
|
||||
@HOST=$$(awk -F'/' '/^host:/ {print $$3}' ~/.startos/config.yaml); \
|
||||
if [ -z "$$HOST" ]; then \
|
||||
echo "Error: You must define \"host: http://server-name.local\" in ~/.startos/config.yaml"; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
S9PK=$$(ls -t *.s9pk 2>/dev/null | head -1); \
|
||||
if [ -z "$$S9PK" ]; then \
|
||||
echo "Error: No .s9pk file found. Run 'make' first."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
printf "\n🚀 Installing %s to %s ...\n" "$$S9PK" "$$HOST"; \
|
||||
start-cli package install -s "$$S9PK"
|
||||
|
||||
publish: | all
|
||||
@REGISTRY=$$(awk -F'/' '/^registry:/ {print $$3}' ~/.startos/config.yaml); \
|
||||
if [ -z "$$REGISTRY" ]; then \
|
||||
echo "Error: You must define \"registry: https://my-registry.tld\" in ~/.startos/config.yaml"; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
S3BASE=$$(awk -F'/' '/^s9pk-s3base:/ {print $$3}' ~/.startos/config.yaml); \
|
||||
if [ -z "$$S3BASE" ]; then \
|
||||
echo "Error: You must define \"s3base: https://s9pks.my-s3-bucket.tld\" in ~/.startos/config.yaml"; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
command -v s3cmd >/dev/null || \
|
||||
(echo "Error: s3cmd not found. It must be installed to publish using s3." && exit 1); \
|
||||
printf "\n🚀 Publishing to %s; indexing on %s ...\n" "$$S3BASE" "$$REGISTRY"; \
|
||||
for s9pk in *.s9pk; do \
|
||||
age=$$(( $$(date +%s) - $$(stat -c %Y "$$s9pk") )); \
|
||||
if [ "$$age" -gt 3600 ]; then \
|
||||
printf "\033[1;33m⚠️ %s is %d minutes old. Publish anyway? [y/N] \033[0m" "$$s9pk" "$$((age / 60))"; \
|
||||
read -r ans; \
|
||||
case "$$ans" in [yY]*) ;; *) echo "Skipping $$s9pk"; continue ;; esac; \
|
||||
fi; \
|
||||
start-cli s9pk publish "$$s9pk"; \
|
||||
done
|
||||
|
||||
check-deps:
|
||||
@command -v start-cli >/dev/null || \
|
||||
(echo "Error: start-cli not found. Please see https://docs.start9.com/latest/developer-guide/sdk/installing-the-sdk" && exit 1)
|
||||
@command -v npm >/dev/null || \
|
||||
(echo "Error: npm not found. Please install Node.js and npm." && exit 1)
|
||||
|
||||
check-init:
|
||||
@if [ ! -f ~/.startos/developer.key.pem ]; then \
|
||||
echo "Initializing StartOS developer environment..."; \
|
||||
start-cli init-key; \
|
||||
fi
|
||||
|
||||
javascript/index.js: $(shell find startos -type f) tsconfig.json node_modules
|
||||
npm run check
|
||||
npm run build
|
||||
|
||||
node_modules: package-lock.json package.json
|
||||
npm ci
|
||||
|
||||
clean:
|
||||
@echo "Cleaning up build artifacts..."
|
||||
@rm -rf $(PACKAGE_ID).s9pk $(PACKAGE_ID)_x86_64.s9pk $(PACKAGE_ID)_aarch64.s9pk $(PACKAGE_ID)_riscv64.s9pk javascript node_modules
|
||||
@@ -0,0 +1,44 @@
|
||||
import { store } from '../fileModels/store'
|
||||
import { i18n } from '../i18n'
|
||||
import { sdk } from '../sdk'
|
||||
|
||||
const { InputSpec, Value } = sdk
|
||||
|
||||
const inputSpec = InputSpec.of({
|
||||
password: Value.text({
|
||||
name: i18n('Password'),
|
||||
description: i18n(
|
||||
'The password Gunner types on the login screen (at least 4 characters)',
|
||||
),
|
||||
required: true,
|
||||
default: null,
|
||||
masked: true,
|
||||
minLength: 4,
|
||||
}),
|
||||
})
|
||||
|
||||
export const setPassword = sdk.Action.withInput(
|
||||
'set-password',
|
||||
|
||||
async ({ effects }) => ({
|
||||
name: i18n('Set Login Password'),
|
||||
description: i18n('Set the password Gunner uses to log in to Premier Gunner'),
|
||||
warning: null,
|
||||
allowedStatuses: 'any',
|
||||
group: null,
|
||||
visibility: 'enabled',
|
||||
}),
|
||||
|
||||
inputSpec,
|
||||
|
||||
async ({ effects }) => {
|
||||
const password = await store.read((s) => s.password).const(effects)
|
||||
return { password: password ?? undefined }
|
||||
},
|
||||
|
||||
async ({ effects, input }) => {
|
||||
await store.merge(effects, { password: input.password })
|
||||
},
|
||||
)
|
||||
|
||||
export const actions = sdk.Actions.of().addAction(setPassword)
|
||||
@@ -0,0 +1,5 @@
|
||||
import { sdk } from './sdk'
|
||||
|
||||
export const { createBackup, restoreInit } = sdk.setupBackups(
|
||||
async ({ effects }) => sdk.Backups.ofVolumes('main'),
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
import { sdk } from './sdk'
|
||||
|
||||
export const setDependencies = sdk.setupDependencies(
|
||||
async ({ effects }) => ({}),
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
Use the `/fileModels` directory to create separate `.ts` files to represent underlying files used by you package. The exported `FileModels` afford a convenient and type safe way to read amd write to the underlying files, as well as react to changes.
|
||||
|
||||
Supported file formats are `.yaml`, `.toml`, `.json`, `.env`, `.ini`, and `.txt`. For alternative file formats, you can use the `raw` method and provide custom serialization and parser functions.
|
||||
|
||||
It is common for packages to use a `store.json.ts` FileModel as a convenient place to persist arbitrary data that are needed by the package but are _not_ persisted by the upstream service. For example, you might use store.json to persist startup flags or login credentials.
|
||||
@@ -0,0 +1,13 @@
|
||||
import { FileHelper, z } from '@start9labs/start-sdk'
|
||||
import { sdk } from '../sdk'
|
||||
|
||||
/**
|
||||
* Persists package data that the upstream app does not store itself.
|
||||
* Here: the login password Premier Gunner injects into the app as PG_PASSWORD.
|
||||
*/
|
||||
export const store = FileHelper.json(
|
||||
{ base: sdk.volumes.main, subpath: 'store.json' },
|
||||
z.object({
|
||||
password: z.string(),
|
||||
}),
|
||||
)
|
||||
@@ -0,0 +1,26 @@
|
||||
export const DEFAULT_LANG = 'en_US'
|
||||
|
||||
const dict = {
|
||||
// main.ts
|
||||
'Starting Premier Gunner!': 0,
|
||||
'Web Interface': 1,
|
||||
'The web interface is ready': 2,
|
||||
'The web interface is not ready': 3,
|
||||
|
||||
// interfaces.ts
|
||||
'Premier Gunner': 4,
|
||||
'The Premier Gunner training tracker web app': 5,
|
||||
|
||||
// actions
|
||||
'Set Login Password': 6,
|
||||
'Set the password Gunner uses to log in to Premier Gunner': 7,
|
||||
'Password': 8,
|
||||
'The password Gunner types on the login screen (at least 4 characters)': 9,
|
||||
} as const
|
||||
|
||||
/**
|
||||
* Plumbing. DO NOT EDIT.
|
||||
*/
|
||||
export type I18nKey = keyof typeof dict
|
||||
export type LangDict = Record<(typeof dict)[I18nKey], string>
|
||||
export default dict
|
||||
@@ -0,0 +1,52 @@
|
||||
import { LangDict } from './default'
|
||||
|
||||
export default {
|
||||
es_ES: {
|
||||
0: '¡Iniciando Premier Gunner!',
|
||||
1: 'Interfaz web',
|
||||
2: 'La interfaz web está lista',
|
||||
3: 'La interfaz web no está lista',
|
||||
4: 'Premier Gunner',
|
||||
5: 'La aplicación web de seguimiento de entrenamiento Premier Gunner',
|
||||
6: 'Establecer contraseña de acceso',
|
||||
7: 'Establece la contraseña que Gunner usa para iniciar sesión en Premier Gunner',
|
||||
8: 'Contraseña',
|
||||
9: 'La contraseña que Gunner escribe en la pantalla de inicio de sesión (al menos 4 caracteres)',
|
||||
},
|
||||
de_DE: {
|
||||
0: 'Starte Premier Gunner!',
|
||||
1: 'Weboberfläche',
|
||||
2: 'Die Weboberfläche ist bereit',
|
||||
3: 'Die Weboberfläche ist nicht bereit',
|
||||
4: 'Premier Gunner',
|
||||
5: 'Die Premier Gunner Trainings-Tracker-Web-App',
|
||||
6: 'Anmeldepasswort festlegen',
|
||||
7: 'Lege das Passwort fest, mit dem Gunner sich bei Premier Gunner anmeldet',
|
||||
8: 'Passwort',
|
||||
9: 'Das Passwort, das Gunner auf dem Anmeldebildschirm eingibt (mindestens 4 Zeichen)',
|
||||
},
|
||||
pl_PL: {
|
||||
0: 'Uruchamianie Premier Gunner!',
|
||||
1: 'Interfejs webowy',
|
||||
2: 'Interfejs webowy jest gotowy',
|
||||
3: 'Interfejs webowy nie jest gotowy',
|
||||
4: 'Premier Gunner',
|
||||
5: 'Aplikacja webowa do śledzenia treningów Premier Gunner',
|
||||
6: 'Ustaw hasło logowania',
|
||||
7: 'Ustaw hasło, którego Gunner używa do logowania w Premier Gunner',
|
||||
8: 'Hasło',
|
||||
9: 'Hasło, które Gunner wpisuje na ekranie logowania (co najmniej 4 znaki)',
|
||||
},
|
||||
fr_FR: {
|
||||
0: 'Démarrage de Premier Gunner !',
|
||||
1: 'Interface web',
|
||||
2: "L'interface web est prête",
|
||||
3: "L'interface web n'est pas prête",
|
||||
4: 'Premier Gunner',
|
||||
5: "L'application web de suivi d'entraînement Premier Gunner",
|
||||
6: 'Définir le mot de passe de connexion',
|
||||
7: 'Définissez le mot de passe que Gunner utilise pour se connecter à Premier Gunner',
|
||||
8: 'Mot de passe',
|
||||
9: "Le mot de passe que Gunner saisit sur l'écran de connexion (au moins 4 caractères)",
|
||||
},
|
||||
} satisfies Record<string, LangDict>
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Plumbing. DO NOT EDIT this file.
|
||||
*/
|
||||
import { setupI18n } from '@start9labs/start-sdk'
|
||||
import defaultDict, { DEFAULT_LANG } from './dictionaries/default'
|
||||
import translations from './dictionaries/translations'
|
||||
|
||||
export const i18n = setupI18n(defaultDict, translations, DEFAULT_LANG)
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Plumbing. DO NOT EDIT.
|
||||
*/
|
||||
export { createBackup } from './backups'
|
||||
export { main } from './main'
|
||||
export { init, uninit } from './init'
|
||||
export { actions } from './actions'
|
||||
import { buildManifest } from '@start9labs/start-sdk'
|
||||
import { manifest as sdkManifest } from './manifest'
|
||||
import { versionGraph } from './versions'
|
||||
export const manifest = buildManifest(versionGraph, sdkManifest)
|
||||
@@ -0,0 +1,16 @@
|
||||
import { sdk } from '../sdk'
|
||||
import { setDependencies } from '../dependencies'
|
||||
import { setInterfaces } from '../interfaces'
|
||||
import { versionGraph } from '../versions'
|
||||
import { actions } from '../actions'
|
||||
import { restoreInit } from '../backups'
|
||||
|
||||
export const init = sdk.setupInit(
|
||||
restoreInit,
|
||||
versionGraph,
|
||||
setInterfaces,
|
||||
setDependencies,
|
||||
actions,
|
||||
)
|
||||
|
||||
export const uninit = sdk.setupUninit(versionGraph)
|
||||
@@ -0,0 +1,25 @@
|
||||
import { i18n } from './i18n'
|
||||
import { sdk } from './sdk'
|
||||
import { uiPort } from './utils'
|
||||
|
||||
export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => {
|
||||
const uiMulti = sdk.MultiHost.of(effects, 'ui-multi')
|
||||
const uiMultiOrigin = await uiMulti.bindPort(uiPort, {
|
||||
protocol: 'http',
|
||||
})
|
||||
const ui = sdk.createInterface(effects, {
|
||||
name: i18n('Premier Gunner'),
|
||||
id: 'ui',
|
||||
description: i18n('The Premier Gunner training tracker web app'),
|
||||
type: 'ui',
|
||||
masked: false,
|
||||
schemeOverride: null,
|
||||
username: null,
|
||||
path: '',
|
||||
query: {},
|
||||
})
|
||||
|
||||
const uiReceipt = await uiMultiOrigin.export([ui])
|
||||
|
||||
return [uiReceipt]
|
||||
})
|
||||
@@ -0,0 +1,47 @@
|
||||
import { store } from './fileModels/store'
|
||||
import { i18n } from './i18n'
|
||||
import { sdk } from './sdk'
|
||||
import { uiPort } from './utils'
|
||||
|
||||
export const main = sdk.setupMain(async ({ effects }) => {
|
||||
console.info(i18n('Starting Premier Gunner!'))
|
||||
|
||||
// The login password lives in store.json. Reading it with `.const` makes the
|
||||
// daemon restart whenever it changes (e.g. via the "Set Login Password" action),
|
||||
// so PG_PASSWORD stays authoritative on every boot.
|
||||
const password = await store.read((s) => s.password).const(effects)
|
||||
|
||||
return sdk.Daemons.of(effects).addDaemon('primary', {
|
||||
subcontainer: await sdk.SubContainer.of(
|
||||
effects,
|
||||
{ imageId: 'premier-gunner' },
|
||||
sdk.Mounts.of().mountVolume({
|
||||
volumeId: 'main',
|
||||
subpath: null,
|
||||
mountpoint: '/data',
|
||||
readonly: false,
|
||||
}),
|
||||
'premier-gunner-sub',
|
||||
),
|
||||
exec: {
|
||||
command: ['node', 'src/server.js'],
|
||||
cwd: '/app',
|
||||
env: {
|
||||
NODE_ENV: 'production',
|
||||
PG_HOST: '0.0.0.0',
|
||||
PG_PORT: String(uiPort),
|
||||
PG_DATA_DIR: '/data',
|
||||
...(password ? { PG_PASSWORD: password } : {}),
|
||||
},
|
||||
},
|
||||
ready: {
|
||||
display: i18n('Web Interface'),
|
||||
fn: () =>
|
||||
sdk.healthCheck.checkPortListening(effects, uiPort, {
|
||||
successMessage: i18n('The web interface is ready'),
|
||||
errorMessage: i18n('The web interface is not ready'),
|
||||
}),
|
||||
},
|
||||
requires: [],
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,20 @@
|
||||
export const short = {
|
||||
en_US: 'A kid-friendly soccer training tracker',
|
||||
es_ES: 'Un rastreador de entrenamiento de fútbol para niños',
|
||||
de_DE: 'Ein kinderfreundlicher Fußball-Trainings-Tracker',
|
||||
pl_PL: 'Przyjazny dzieciom tracker treningu piłkarskiego',
|
||||
fr_FR: "Un suivi d'entraînement de football adapté aux enfants",
|
||||
}
|
||||
|
||||
export const long = {
|
||||
en_US:
|
||||
'Premier Gunner is a kid-friendly, mobile-friendly soccer training tracker. Log daily training across categories, plan future sessions, set goals, and watch progress climb toward the big reward.',
|
||||
es_ES:
|
||||
'Premier Gunner es un rastreador de entrenamiento de fútbol adaptado a niños y móviles. Registra el entrenamiento diario por categorías, planifica sesiones futuras, fija metas y observa el progreso hacia la gran recompensa.',
|
||||
de_DE:
|
||||
'Premier Gunner ist ein kinder- und mobilfreundlicher Fußball-Trainings-Tracker. Erfasse das tägliche Training nach Kategorien, plane zukünftige Einheiten, setze Ziele und verfolge den Fortschritt zur großen Belohnung.',
|
||||
pl_PL:
|
||||
'Premier Gunner to przyjazny dzieciom i urządzeniom mobilnym tracker treningu piłkarskiego. Zapisuj codzienny trening w kategoriach, planuj przyszłe sesje, ustawiaj cele i obserwuj postępy w drodze do wielkiej nagrody.',
|
||||
fr_FR:
|
||||
"Premier Gunner est un suivi d'entraînement de football adapté aux enfants et aux mobiles. Enregistrez l'entraînement quotidien par catégories, planifiez les séances futures, fixez des objectifs et suivez les progrès vers la grande récompense.",
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { setupManifest } from '@start9labs/start-sdk'
|
||||
import { long, short } from './i18n'
|
||||
|
||||
export const manifest = setupManifest({
|
||||
id: 'premier-gunner',
|
||||
title: 'Premier Gunner',
|
||||
license: 'MIT',
|
||||
packageRepo: 'https://github.com/ten31/premier-gunner',
|
||||
upstreamRepo: 'https://github.com/ten31/premier-gunner',
|
||||
marketingUrl: 'https://github.com/ten31/premier-gunner',
|
||||
donationUrl: null,
|
||||
description: { short, long },
|
||||
volumes: ['main'],
|
||||
images: {
|
||||
'premier-gunner': {
|
||||
source: { dockerBuild: { dockerfile: 'Dockerfile', workdir: '.' } },
|
||||
arch: ['x86_64', 'aarch64'],
|
||||
},
|
||||
},
|
||||
alerts: {
|
||||
install: null,
|
||||
update: null,
|
||||
uninstall: null,
|
||||
restore: null,
|
||||
start: null,
|
||||
stop: null,
|
||||
},
|
||||
dependencies: {},
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
import { StartSdk } from '@start9labs/start-sdk'
|
||||
import { manifest } from './manifest'
|
||||
|
||||
/**
|
||||
* Plumbing. DO NOT EDIT.
|
||||
*
|
||||
* The exported "sdk" const is used throughout this package codebase.
|
||||
*/
|
||||
export const sdk = StartSdk.of().withManifest(manifest).build(true)
|
||||
@@ -0,0 +1,5 @@
|
||||
// Here we define any constants or functions that are shared by multiple components
|
||||
// throughout the package codebase.
|
||||
|
||||
// The port the Premier Gunner Node server listens on inside the container.
|
||||
export const uiPort = 3000
|
||||
@@ -0,0 +1,28 @@
|
||||
import { IMPOSSIBLE, utils, VersionInfo } from '@start9labs/start-sdk'
|
||||
import { store } from '../fileModels/store'
|
||||
|
||||
export const current = VersionInfo.of({
|
||||
version: '0.1.0:0',
|
||||
releaseNotes: {
|
||||
en_US: 'Initial release of Premier Gunner for StartOS.',
|
||||
es_ES: 'Versión inicial de Premier Gunner para StartOS.',
|
||||
de_DE: 'Erste Veröffentlichung von Premier Gunner für StartOS.',
|
||||
pl_PL: 'Pierwsze wydanie Premier Gunner dla StartOS.',
|
||||
fr_FR: 'Première version de Premier Gunner pour StartOS.',
|
||||
},
|
||||
migrations: {
|
||||
up: async ({ effects }) => {
|
||||
// Generate a random login password on first install so the app is never
|
||||
// left on a known default. The user can change it via "Set Login Password".
|
||||
const existing = await store.read().once()
|
||||
if (!existing) {
|
||||
const password = utils.getDefaultString({
|
||||
charset: 'a-z,A-Z,2-9',
|
||||
len: 16,
|
||||
})
|
||||
await store.write(effects, { password })
|
||||
}
|
||||
},
|
||||
down: IMPOSSIBLE,
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,7 @@
|
||||
import { VersionGraph } from '@start9labs/start-sdk'
|
||||
import { current } from './current'
|
||||
|
||||
export const versionGraph = VersionGraph.of({
|
||||
current,
|
||||
other: [],
|
||||
})
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"include": ["startos/**/*.ts", "node_modules/**/startos"],
|
||||
"compilerOptions": {
|
||||
"target": "ES2018",
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user