From 58d3c1ba598e486afbe259589984b8998a009c05 Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Tue, 13 May 2025 10:11:00 -0600 Subject: [PATCH 01/49] update upstream to 1.1.0 --- Dockerfile | 4 ++-- manifest.yaml | 6 ++---- scripts/services/migrations.ts | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index b39c6e7..c93065b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM lnbits/lnbits:v1.0.0 AS builder +FROM lnbits/lnbits:v1.1.0 AS builder # arm64 or amd64 ARG PLATFORM @@ -6,7 +6,7 @@ ARG PLATFORM RUN apt-get update && apt-get install -y bash curl sqlite3 tini --no-install-recommends RUN curl -sS https://webi.sh/yq | sh -FROM lnbits/lnbits:v1.0.0 AS final +FROM lnbits/lnbits:v1.1.0 AS final COPY --from=builder /usr/bin/tini /usr/bin/tini COPY --from=builder /usr/bin/sqlite3 /usr/bin/sqlite3 diff --git a/manifest.yaml b/manifest.yaml index 6754fa0..74da358 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -1,10 +1,8 @@ id: lnbits title: "LNBits" -version: 1.0.0 +version: 1.1.0 release-notes: | - * Update to v1.0.0 [release notes](https://github.com/lnbits/lnbits/releases/tag/v1.0.0) - * NOTE: If the UI shows a 401 error after updating you will need to use the icon in the upper right to logout before logging back in - * Bump LND max version + * Update to v1.1.0 [release notes](https://github.com/lnbits/lnbits/releases/tag/v1.1.0) license: MIT wrapper-repo: "https://github.com/Start9Labs/lnbits-startos" upstream-repo: "https://github.com/lnbits/lnbits" diff --git a/scripts/services/migrations.ts b/scripts/services/migrations.ts index 4719a23..e1a4577 100644 --- a/scripts/services/migrations.ts +++ b/scripts/services/migrations.ts @@ -33,5 +33,5 @@ export const migration: T.ExpectedExports.migration = }, }, }, - "1.0.0" + "1.1.0" ); From 46b5c9e28ddbe1ae721adb5bd18e0402d5bf7b58 Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Wed, 20 Aug 2025 13:44:43 -0600 Subject: [PATCH 02/49] feature complete for 040 --- .gitignore | 7 +- Dockerfile | 14 +- Makefile | 127 +- .env.example => Old/.env.example | 0 Old/Makefile | 70 + {actions => Old/actions}/reset-pass.sh | 0 check-web.sh => Old/check-web.sh | 0 .../docker_entrypoint.sh | 0 manifest.yaml => Old/manifest.yaml | 0 {scripts => Old/scripts}/bundle.ts | 0 {scripts => Old/scripts}/deps.ts | 0 Old/scripts/embassy.js | 4014 +++++++++++++++++ {scripts => Old/scripts}/embassy.ts | 0 .../scripts}/services/getConfig.ts | 0 .../scripts}/services/healthChecks.ts | 0 .../scripts}/services/migrations.ts | 0 .../scripts}/services/properties.ts | 0 .../scripts}/services/setConfig.ts | 0 {.github => Old}/workflows/buildService.yml | 0 {.github => Old}/workflows/releaseService.yml | 0 README.md | 62 +- package-lock.json | 278 ++ package.json | 25 + startos/actions/index.ts | 7 + startos/actions/lightningImplementation.ts | 74 + startos/actions/resetPassword.ts | 79 + startos/backups.ts | 5 + startos/dependencies.ts | 27 + startos/fileModels/env.ts | 130 + startos/index.ts | 11 + startos/init/index.ts | 18 + startos/init/taskSetLnImplementation.ts | 12 + startos/install/versionGraph.ts | 16 + startos/install/versions/index.ts | 2 + startos/install/versions/v1_2_1_1.ts | 38 + startos/interfaces.ts | 24 + startos/main.ts | 58 + startos/manifest.ts | 57 + startos/sdk.ts | 9 + startos/utils.ts | 85 + tsconfig.json | 11 + 41 files changed, 5128 insertions(+), 132 deletions(-) rename .env.example => Old/.env.example (100%) create mode 100644 Old/Makefile rename {actions => Old/actions}/reset-pass.sh (100%) rename check-web.sh => Old/check-web.sh (100%) rename docker_entrypoint.sh => Old/docker_entrypoint.sh (100%) rename manifest.yaml => Old/manifest.yaml (100%) rename {scripts => Old/scripts}/bundle.ts (100%) rename {scripts => Old/scripts}/deps.ts (100%) create mode 100644 Old/scripts/embassy.js rename {scripts => Old/scripts}/embassy.ts (100%) rename {scripts => Old/scripts}/services/getConfig.ts (100%) rename {scripts => Old/scripts}/services/healthChecks.ts (100%) rename {scripts => Old/scripts}/services/migrations.ts (100%) rename {scripts => Old/scripts}/services/properties.ts (100%) rename {scripts => Old/scripts}/services/setConfig.ts (100%) rename {.github => Old}/workflows/buildService.yml (100%) rename {.github => Old}/workflows/releaseService.yml (100%) create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 startos/actions/index.ts create mode 100644 startos/actions/lightningImplementation.ts create mode 100644 startos/actions/resetPassword.ts create mode 100644 startos/backups.ts create mode 100644 startos/dependencies.ts create mode 100644 startos/fileModels/env.ts create mode 100644 startos/index.ts create mode 100644 startos/init/index.ts create mode 100644 startos/init/taskSetLnImplementation.ts create mode 100644 startos/install/versionGraph.ts create mode 100644 startos/install/versions/index.ts create mode 100644 startos/install/versions/v1_2_1_1.ts create mode 100644 startos/interfaces.ts create mode 100644 startos/main.ts create mode 100644 startos/manifest.ts create mode 100644 startos/sdk.ts create mode 100644 startos/utils.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index 329495d..4dad48c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ *.s9pk -scripts/*.js +startos/*.js +node_modules/ .DS_Store .vscode/ -docker-images/ +docker-images +javascript +ncc-cache \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index c93065b..45a7f19 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM lnbits/lnbits:v1.1.0 AS builder +FROM lnbits/lnbits:v1.2.1 AS builder # arm64 or amd64 ARG PLATFORM @@ -6,7 +6,7 @@ ARG PLATFORM RUN apt-get update && apt-get install -y bash curl sqlite3 tini --no-install-recommends RUN curl -sS https://webi.sh/yq | sh -FROM lnbits/lnbits:v1.1.0 AS final +FROM lnbits/lnbits:v1.2.1 AS final COPY --from=builder /usr/bin/tini /usr/bin/tini COPY --from=builder /usr/bin/sqlite3 /usr/bin/sqlite3 @@ -20,14 +20,4 @@ RUN apt-get update && \ RUN pip3 install bcrypt -ENV LNBITS_PORT=5000 -ENV LNBITS_HOST=lnbits.embassy - WORKDIR /app/ -RUN mkdir -p ./data -ADD .env.example ./.env -ADD actions/*.sh /usr/local/bin/ -RUN chmod a+x ./.env -ADD docker_entrypoint.sh /usr/local/bin/docker_entrypoint.sh -ADD check-web.sh /usr/local/bin/check-web.sh -RUN chmod a+x /usr/local/bin/*.sh diff --git a/Makefile b/Makefile index 05be490..94ca408 100644 --- a/Makefile +++ b/Makefile @@ -1,70 +1,83 @@ -PKG_ID := $(shell yq e ".id" manifest.yaml) -PKG_VERSION := $(shell yq e ".version" manifest.yaml) -TS_FILES := $(shell find ./ -name \*.ts) +PACKAGE_ID := $(shell awk -F"'" '/id:/ {print $$2}' startos/manifest.ts) +INGREDIENTS := $(shell start-cli s9pk list-ingredients 2>/dev/null) -# delete the target of a rule if it has changed and its recipe exits with a nonzero exit status +CMD_ARCH_GOAL := $(filter aarch64 x86_64, $(MAKECMDGOALS)) +ifeq ($(CMD_ARCH_GOAL),) + BUILD := universal + S9PK := $(PACKAGE_ID).s9pk +else + BUILD := $(firstword $(CMD_ARCH_GOAL)) + S9PK := $(PACKAGE_ID)_$(BUILD).s9pk +endif + +.PHONY: all aarch64 x86_64 clean install check-deps check-init package ingredients .DELETE_ON_ERROR: -all: submodule-update verify +define SUMMARY + @manifest=$$(start-cli s9pk inspect $(1) manifest); \ + size=$$(du -h $(1) | awk '{print $$1}'); \ + title=$$(echo $$manifest | jq -r .title); \ + version=$$(echo $$manifest | jq -r .version); \ + arches=$$(echo $$manifest | jq -r '.hardwareRequirements.arch | join(", ")'); \ + sdkv=$$(echo $$manifest | jq -r .sdkVersion); \ + gitHash=$$(echo "$$manifest" | jq -r .gitHash | sed -E 's/(.*-modified)$$/\x1b[0;31m\1\x1b[0m/'); \ + echo ""; \ + echo "\033[1;32m✅ Build Complete!\033[0m"; \ + echo ""; \ + echo "\033[1;37m📦 $$title\033[0m \033[36mv$$version\033[0m"; \ + echo "───────────────────────────────"; \ + 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 -verify: $(PKG_ID).s9pk - @start-sdk verify s9pk $(PKG_ID).s9pk - @echo " Done!" - @echo " Filesize: $(shell du -h $(PKG_ID).s9pk) is ready" +all: $(PACKAGE_ID).s9pk + $(call SUMMARY,$(S9PK)) -install: -ifeq (,$(wildcard ~/.embassy/config.yaml)) - @echo; echo "You must define \"host: http://server-name.local\" in ~/.embassy/config.yaml config file first"; echo -else - start-cli package install $(PKG_ID).s9pk -endif +$(BUILD): $(PACKAGE_ID)_$(BUILD).s9pk + $(call SUMMARY,$(S9PK)) -clean: - rm -rf docker-images - rm -f $(PKG_ID).s9pk - rm -f scripts/*.js +$(S9PK): $(INGREDIENTS) .git/HEAD .git/index + @$(MAKE) --no-print-directory ingredients + @echo " Packing '$(S9PK)'..." + BUILD=$(BUILD) start-cli s9pk pack -o $(S9PK) -submodule-update: - @if [ -z "$(shell git submodule status | egrep -v '^ '|awk '{print $$2}')" ]; then \ - echo "Submodules are up to date."; \ - else \ - echo "\nUpdating submodules...\n"; \ - git submodule update --init --progress; \ - fi +ingredients: $(INGREDIENTS) + @echo " Re-evaluating ingredients..." + +install: package | 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; \ + echo "\n🚀 Installing to $$HOST ..."; \ + start-cli package install -s $(S9PK) -scripts/embassy.js: $(TS_FILES) - deno run --allow-read --allow-write --allow-env --allow-net scripts/bundle.ts +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) -# for rebuilding just the arm image. will include docker-images/x86_64.tar into the s9pk if it exists -arm: - @rm -f docker-images/x86_64.tar - ARCH=aarch64 $(MAKE) +check-init: + @if [ ! -f ~/.startos/developer.key.pem ]; then \ + echo "Initializing StartOS developer environment..."; \ + start-cli init; \ + fi -# for rebuilding just the x86 image. will include docker-images/aarch64.tar into the s9pk if it exists -x86: - @rm -f docker-images/aarch64.tar - ARCH=x86_64 $(MAKE) +javascript/index.js: $(shell find startos -type f) tsconfig.json node_modules + npm run build -docker-images/x86_64.tar: Dockerfile docker_entrypoint.sh -ifeq ($(ARCH),aarch64) -else - mkdir -p docker-images - docker buildx build --tag start9/$(PKG_ID)/main:$(PKG_VERSION) --platform=linux/amd64 --build-arg PLATFORM=amd64 -o type=docker,dest=docker-images/x86_64.tar . -endif +node_modules: package-lock.json + npm ci -docker-images/aarch64.tar: Dockerfile docker_entrypoint.sh -ifeq ($(ARCH),x86_64) -else - mkdir -p docker-images - docker buildx build --tag start9/$(PKG_ID)/main:$(PKG_VERSION) --platform=linux/arm64 --build-arg PLATFORM=arm64 -o type=docker,dest=docker-images/aarch64.tar . -endif +package-lock.json: package.json + npm i -$(PKG_ID).s9pk: manifest.yaml instructions.md LICENSE icon.png scripts/embassy.js docker-images/aarch64.tar docker-images/x86_64.tar -ifeq ($(ARCH),aarch64) - @echo "start-sdk: Preparing aarch64 package ..." -else ifeq ($(ARCH),x86_64) - @echo "start-sdk: Preparing x86_64 package ..." -else - @echo "start-sdk: Preparing Universal Package ..." -endif - @start-sdk pack +clean: + @echo "Cleaning up build artifacts..." + @rm -rf $(PACKAGE_ID).s9pk $(PACKAGE_ID)_x86_64.s9pk $(PACKAGE_ID)_aarch64.s9pk javascript node_modules \ No newline at end of file diff --git a/.env.example b/Old/.env.example similarity index 100% rename from .env.example rename to Old/.env.example diff --git a/Old/Makefile b/Old/Makefile new file mode 100644 index 0000000..05be490 --- /dev/null +++ b/Old/Makefile @@ -0,0 +1,70 @@ +PKG_ID := $(shell yq e ".id" manifest.yaml) +PKG_VERSION := $(shell yq e ".version" manifest.yaml) +TS_FILES := $(shell find ./ -name \*.ts) + +# delete the target of a rule if it has changed and its recipe exits with a nonzero exit status +.DELETE_ON_ERROR: + +all: submodule-update verify + +verify: $(PKG_ID).s9pk + @start-sdk verify s9pk $(PKG_ID).s9pk + @echo " Done!" + @echo " Filesize: $(shell du -h $(PKG_ID).s9pk) is ready" + +install: +ifeq (,$(wildcard ~/.embassy/config.yaml)) + @echo; echo "You must define \"host: http://server-name.local\" in ~/.embassy/config.yaml config file first"; echo +else + start-cli package install $(PKG_ID).s9pk +endif + +clean: + rm -rf docker-images + rm -f $(PKG_ID).s9pk + rm -f scripts/*.js + +submodule-update: + @if [ -z "$(shell git submodule status | egrep -v '^ '|awk '{print $$2}')" ]; then \ + echo "Submodules are up to date."; \ + else \ + echo "\nUpdating submodules...\n"; \ + git submodule update --init --progress; \ + fi + +scripts/embassy.js: $(TS_FILES) + deno run --allow-read --allow-write --allow-env --allow-net scripts/bundle.ts + +# for rebuilding just the arm image. will include docker-images/x86_64.tar into the s9pk if it exists +arm: + @rm -f docker-images/x86_64.tar + ARCH=aarch64 $(MAKE) + +# for rebuilding just the x86 image. will include docker-images/aarch64.tar into the s9pk if it exists +x86: + @rm -f docker-images/aarch64.tar + ARCH=x86_64 $(MAKE) + +docker-images/x86_64.tar: Dockerfile docker_entrypoint.sh +ifeq ($(ARCH),aarch64) +else + mkdir -p docker-images + docker buildx build --tag start9/$(PKG_ID)/main:$(PKG_VERSION) --platform=linux/amd64 --build-arg PLATFORM=amd64 -o type=docker,dest=docker-images/x86_64.tar . +endif + +docker-images/aarch64.tar: Dockerfile docker_entrypoint.sh +ifeq ($(ARCH),x86_64) +else + mkdir -p docker-images + docker buildx build --tag start9/$(PKG_ID)/main:$(PKG_VERSION) --platform=linux/arm64 --build-arg PLATFORM=arm64 -o type=docker,dest=docker-images/aarch64.tar . +endif + +$(PKG_ID).s9pk: manifest.yaml instructions.md LICENSE icon.png scripts/embassy.js docker-images/aarch64.tar docker-images/x86_64.tar +ifeq ($(ARCH),aarch64) + @echo "start-sdk: Preparing aarch64 package ..." +else ifeq ($(ARCH),x86_64) + @echo "start-sdk: Preparing x86_64 package ..." +else + @echo "start-sdk: Preparing Universal Package ..." +endif + @start-sdk pack diff --git a/actions/reset-pass.sh b/Old/actions/reset-pass.sh similarity index 100% rename from actions/reset-pass.sh rename to Old/actions/reset-pass.sh diff --git a/check-web.sh b/Old/check-web.sh similarity index 100% rename from check-web.sh rename to Old/check-web.sh diff --git a/docker_entrypoint.sh b/Old/docker_entrypoint.sh similarity index 100% rename from docker_entrypoint.sh rename to Old/docker_entrypoint.sh diff --git a/manifest.yaml b/Old/manifest.yaml similarity index 100% rename from manifest.yaml rename to Old/manifest.yaml diff --git a/scripts/bundle.ts b/Old/scripts/bundle.ts similarity index 100% rename from scripts/bundle.ts rename to Old/scripts/bundle.ts diff --git a/scripts/deps.ts b/Old/scripts/deps.ts similarity index 100% rename from scripts/deps.ts rename to Old/scripts/deps.ts diff --git a/Old/scripts/embassy.js b/Old/scripts/embassy.js new file mode 100644 index 0000000..d33355d --- /dev/null +++ b/Old/scripts/embassy.js @@ -0,0 +1,4014 @@ +function saferStringify(x) { + try { + return JSON.stringify(x); + } catch (e) { + return "" + x; + } +} +class AnyParser { + description; + constructor(description = { + name: "Any", + children: [], + extras: [] + }){ + this.description = description; + } + parse(a, onParse) { + return onParse.parsed(a); + } +} +class ArrayParser { + description; + constructor(description = { + name: "Array", + children: [], + extras: [] + }){ + this.description = description; + } + parse(a, onParse) { + if (Array.isArray(a)) return onParse.parsed(a); + return onParse.invalid({ + value: a, + keys: [], + parser: this + }); + } +} +class BoolParser { + description; + constructor(description = { + name: "Boolean", + children: [], + extras: [] + }){ + this.description = description; + } + parse(a, onParse) { + if (a === true || a === false) return onParse.parsed(a); + return onParse.invalid({ + value: a, + keys: [], + parser: this + }); + } +} +const isObject = (x)=>typeof x === "object" && x != null; +const isFunctionTest = (x)=>typeof x === "function"; +const isNumber = (x)=>typeof x === "number"; +const isString = (x)=>typeof x === "string"; +const booleanOnParse = { + parsed (_) { + return true; + }, + invalid (_) { + return false; + } +}; +class FunctionParser { + description; + constructor(description = { + name: "Function", + children: [], + extras: [] + }){ + this.description = description; + } + parse(a, onParse) { + if (isFunctionTest(a)) return onParse.parsed(a); + return onParse.invalid({ + value: a, + keys: [], + parser: this + }); + } +} +class NilParser { + description; + constructor(description = { + name: "Null", + children: [], + extras: [] + }){ + this.description = description; + } + parse(a, onParse) { + if (a === null || a === undefined) return onParse.parsed(a); + return onParse.invalid({ + value: a, + keys: [], + parser: this + }); + } +} +class ObjectParser { + description; + constructor(description = { + name: "Object", + children: [], + extras: [] + }){ + this.description = description; + } + parse(a, onParse) { + if (isObject(a)) return onParse.parsed(a); + return onParse.invalid({ + value: a, + keys: [], + parser: this + }); + } +} +class StringParser { + description; + constructor(description = { + name: "String", + children: [], + extras: [] + }){ + this.description = description; + } + parse(a, onParse) { + if (isString(a)) return onParse.parsed(a); + return onParse.invalid({ + value: a, + keys: [], + parser: this + }); + } +} +class UnknownParser { + description; + constructor(description = { + name: "Unknown", + children: [], + extras: [] + }){ + this.description = description; + } + parse(a, onParse) { + return onParse.parsed(a); + } +} +class ConcatParsers { + parent; + otherParser; + description; + constructor(parent, otherParser, description = { + name: "Concat", + children: [ + parent, + otherParser + ], + extras: [] + }){ + this.parent = parent; + this.otherParser = otherParser; + this.description = description; + } + static of(parent, otherParser) { + if (parent.unwrappedParser().description.name === "Any") { + return otherParser; + } + if (otherParser.unwrappedParser().description.name === "Any") { + return parent; + } + return new ConcatParsers(parent, otherParser); + } + parse(a, onParse) { + const parent = this.parent.enumParsed(a); + if ("error" in parent) { + return onParse.invalid(parent.error); + } + const other = this.otherParser.enumParsed(parent.value); + if ("error" in other) { + return onParse.invalid(other.error); + } + return onParse.parsed(other.value); + } +} +class DefaultParser { + parent; + defaultValue; + description; + constructor(parent, defaultValue, description = { + name: "Default", + children: [ + parent + ], + extras: [ + defaultValue + ] + }){ + this.parent = parent; + this.defaultValue = defaultValue; + this.description = description; + } + parse(a, onParse) { + const parser = this; + const defaultValue = this.defaultValue; + if (a == null) { + return onParse.parsed(defaultValue); + } + const parentCheck = this.parent.enumParsed(a); + if ("error" in parentCheck) { + parentCheck.error.parser = parser; + return onParse.invalid(parentCheck.error); + } + return onParse.parsed(parentCheck.value); + } +} +class GuardParser { + checkIsA; + typeName; + description; + constructor(checkIsA, typeName, description = { + name: "Guard", + children: [], + extras: [ + typeName + ] + }){ + this.checkIsA = checkIsA; + this.typeName = typeName; + this.description = description; + } + parse(a, onParse) { + if (this.checkIsA(a)) { + return onParse.parsed(a); + } + return onParse.invalid({ + value: a, + keys: [], + parser: this + }); + } +} +class MappedAParser { + parent; + map; + mappingName; + description; + constructor(parent, map, mappingName = map.name, description = { + name: "Mapped", + children: [ + parent + ], + extras: [ + mappingName + ] + }){ + this.parent = parent; + this.map = map; + this.mappingName = mappingName; + this.description = description; + } + parse(a, onParse) { + const map = this.map; + const result = this.parent.enumParsed(a); + if ("error" in result) { + return onParse.invalid(result.error); + } + return onParse.parsed(map(result.value)); + } +} +class MaybeParser { + parent; + description; + constructor(parent, description = { + name: "Maybe", + children: [ + parent + ], + extras: [] + }){ + this.parent = parent; + this.description = description; + } + parse(a, onParse) { + if (a == null) { + return onParse.parsed(null); + } + const parser = this; + const parentState = this.parent.enumParsed(a); + if ("error" in parentState) { + const { error } = parentState; + error.parser = parser; + return onParse.invalid(error); + } + return onParse.parsed(parentState.value); + } +} +class OrParsers { + parent; + otherParser; + description; + constructor(parent, otherParser, description = { + name: "Or", + children: [ + parent, + otherParser + ], + extras: [] + }){ + this.parent = parent; + this.otherParser = otherParser; + this.description = description; + } + parse(a, onParse) { + const parser = this; + const parent = this.parent.enumParsed(a); + if ("value" in parent) { + return onParse.parsed(parent.value); + } + const other = this.otherParser.enumParsed(a); + if ("error" in other) { + const { error } = other; + error.parser = parser; + return onParse.invalid(error); + } + return onParse.parsed(other.value); + } +} +class NumberParser { + description; + constructor(description = { + name: "Number", + children: [], + extras: [] + }){ + this.description = description; + } + parse(a, onParse) { + if (isNumber(a)) return onParse.parsed(a); + return onParse.invalid({ + value: a, + keys: [], + parser: this + }); + } +} +function unwrapParser(a) { + if (a instanceof Parser) return unwrapParser(a.parser); + return a; +} +const enumParsed = { + parsed (value) { + return { + value + }; + }, + invalid (error) { + return { + error + }; + } +}; +class Parser { + parser; + description; + _TYPE; + constructor(parser, description = { + name: "Wrapper", + children: [ + parser + ], + extras: [] + }){ + this.parser = parser; + this.description = description; + this._TYPE = null; + this.test = (value)=>{ + return this.parse(value, booleanOnParse); + }; + } + parse(a, onParse) { + return this.parser.parse(a, onParse); + } + static isA(checkIsA, name) { + return new Parser(new GuardParser(checkIsA, name)); + } + static validatorErrorAsString = (error)=>{ + const { parser, value, keys } = error; + const keysString = !keys.length ? "" : keys.map((x)=>`[${x}]`).reverse().join(""); + return `${keysString}${Parser.parserAsString(parser)}(${saferStringify(value)})`; + }; + static parserAsString(parserComingIn) { + const parser = unwrapParser(parserComingIn); + const { description: { name, extras, children } } = parser; + if (parser instanceof ShapeParser) { + return `${name}<{${parser.description.children.map((subParser, i)=>`${String(parser.description.extras[i]) || "?"}:${Parser.parserAsString(subParser)}`).join(",")}}>`; + } + if (parser instanceof OrParsers) { + const parent = unwrapParser(parser.parent); + const parentString = Parser.parserAsString(parent); + if (parent instanceof OrParsers) return parentString; + return `${name}<${parentString},...>`; + } + if (parser instanceof GuardParser) { + return String(extras[0] || name); + } + if (parser instanceof StringParser || parser instanceof ObjectParser || parser instanceof NumberParser || parser instanceof BoolParser || parser instanceof AnyParser) { + return name.toLowerCase(); + } + if (parser instanceof FunctionParser) { + return name; + } + if (parser instanceof NilParser) { + return "null"; + } + if (parser instanceof ArrayParser) { + return "Array"; + } + const specifiers = [ + ...extras.map(saferStringify), + ...children.map(Parser.parserAsString) + ]; + const specifiersString = `<${specifiers.join(",")}>`; + !children.length ? "" : `<>`; + return `${name}${specifiersString}`; + } + unsafeCast(value) { + const state = this.enumParsed(value); + if ("value" in state) return state.value; + const { error } = state; + throw new TypeError(`Failed type: ${Parser.validatorErrorAsString(error)} given input ${saferStringify(value)}`); + } + castPromise(value) { + const state = this.enumParsed(value); + if ("value" in state) return Promise.resolve(state.value); + const { error } = state; + return Promise.reject(new TypeError(`Failed type: ${Parser.validatorErrorAsString(error)} given input ${saferStringify(value)}`)); + } + errorMessage(input) { + const parsed = this.parse(input, enumParsed); + if ("value" in parsed) return; + return Parser.validatorErrorAsString(parsed.error); + } + map(fn, mappingName) { + return new Parser(new MappedAParser(this, fn, mappingName)); + } + concat(otherParser) { + return new Parser(ConcatParsers.of(this, new Parser(otherParser))); + } + orParser(otherParser) { + return new Parser(new OrParsers(this, new Parser(otherParser))); + } + test; + optional(name) { + return new Parser(new MaybeParser(this)); + } + defaultTo(defaultValue) { + return new Parser(new DefaultParser(new Parser(new MaybeParser(this)), defaultValue)); + } + validate(isValid, otherName) { + return new Parser(ConcatParsers.of(this, new Parser(new GuardParser(isValid, otherName)))); + } + refine(refinementTest, otherName = refinementTest.name) { + return new Parser(ConcatParsers.of(this, new Parser(new GuardParser(refinementTest, otherName)))); + } + name(nameString) { + return parserName(nameString, this); + } + enumParsed(value) { + return this.parse(value, enumParsed); + } + unwrappedParser() { + let answer = this; + while(true){ + const next = answer.parser; + if (next instanceof Parser) { + answer = next; + } else { + return next; + } + } + } +} +function guard(test, testName) { + return Parser.isA(test, testName || test.name); +} +const any = new Parser(new AnyParser()); +class ArrayOfParser { + parser; + description; + constructor(parser, description = { + name: "ArrayOf", + children: [ + parser + ], + extras: [] + }){ + this.parser = parser; + this.description = description; + } + parse(a, onParse) { + if (!Array.isArray(a)) { + return onParse.invalid({ + value: a, + keys: [], + parser: this + }); + } + const values = [ + ...a + ]; + for(let index = 0; index < values.length; index++){ + const result = this.parser.enumParsed(values[index]); + if ("error" in result) { + result.error.keys.push("" + index); + return onParse.invalid(result.error); + } else { + values[index] = result.value; + } + } + return onParse.parsed(values); + } +} +function arrayOf(validator) { + return new Parser(new ArrayOfParser(validator)); +} +const unknown = new Parser(new UnknownParser()); +const number = new Parser(new NumberParser()); +const isNill = new Parser(new NilParser()); +const natural = number.refine((x)=>x >= 0 && x === Math.floor(x)); +const isFunction = new Parser(new FunctionParser()); +const __boolean = new Parser(new BoolParser()); +class DeferredParser { + description; + parser; + static create() { + return new DeferredParser(); + } + constructor(description = { + name: "Deferred", + children: [], + extras: [] + }){ + this.description = description; + } + setParser(parser) { + this.parser = new Parser(parser); + return this; + } + parse(a, onParse) { + if (!this.parser) { + return onParse.invalid({ + value: "Not Set Up", + keys: [], + parser: this + }); + } + return this.parser.parse(a, onParse); + } +} +function deferred() { + const deferred = DeferredParser.create(); + function setParser(parser) { + deferred.setParser(parser); + } + return [ + new Parser(deferred), + setParser + ]; +} +const object = new Parser(new ObjectParser()); +class DictionaryParser { + parsers; + description; + constructor(parsers, description = { + name: "Dictionary", + children: parsers.reduce((acc, [k, v])=>{ + acc.push(k, v); + return acc; + }, []), + extras: [] + }){ + this.parsers = parsers; + this.description = description; + } + parse(a, onParse) { + const { parsers } = this; + const parser = this; + const answer = { + ...a + }; + outer: for(const key in a){ + let parseError = []; + for (const [keyParser, valueParser] of parsers){ + const enumState = keyParser.enumParsed(key); + if ("error" in enumState) { + const { error } = enumState; + error.parser = parser; + error.keys.push("" + key); + parseError.push(error); + continue; + } + const newKey = enumState.value; + const valueState = valueParser.enumParsed(a[key]); + if ("error" in valueState) { + const { error } = valueState; + error.keys.push("" + newKey); + parseError.unshift(error); + continue; + } + delete answer[key]; + answer[newKey] = valueState.value; + break outer; + } + const error = parseError[0]; + if (!!error) { + return onParse.invalid(error); + } + } + return onParse.parsed(answer); + } +} +const dictionary = (...parsers)=>{ + return object.concat(new DictionaryParser([ + ...parsers + ])); +}; +function every(...parsers) { + const filteredParsers = parsers.filter((x)=>x !== any); + if (filteredParsers.length <= 0) { + return any; + } + const first = filteredParsers.splice(0, 1)[0]; + return filteredParsers.reduce((left, right)=>{ + return left.concat(right); + }, first); +} +const isArray = new Parser(new ArrayParser()); +const string = new Parser(new StringParser()); +const instanceOf = (classCreator)=>guard((x)=>x instanceof classCreator, `is${classCreator.name}`); +class LiteralsParser { + values; + description; + constructor(values, description = { + name: "Literal", + children: [], + extras: values + }){ + this.values = values; + this.description = description; + } + parse(a, onParse) { + if (this.values.indexOf(a) >= 0) { + return onParse.parsed(a); + } + return onParse.invalid({ + value: a, + keys: [], + parser: this + }); + } +} +function literal(isEqualToValue) { + return new Parser(new LiteralsParser([ + isEqualToValue + ])); +} +function literals(firstValue, ...restValues) { + return new Parser(new LiteralsParser([ + firstValue, + ...restValues + ])); +} +class ShapeParser { + parserMap; + isPartial; + parserKeys; + description; + constructor(parserMap, isPartial, parserKeys = Object.keys(parserMap), description = { + name: isPartial ? "Partial" : "Shape", + children: parserKeys.map((key)=>parserMap[key]), + extras: parserKeys + }){ + this.parserMap = parserMap; + this.isPartial = isPartial; + this.parserKeys = parserKeys; + this.description = description; + } + parse(a, onParse) { + const parser = this; + if (!object.test(a)) { + return onParse.invalid({ + value: a, + keys: [], + parser + }); + } + const { parserMap, isPartial } = this; + const value = { + ...a + }; + if (Array.isArray(a)) { + value.length = a.length; + } + for(const key in parserMap){ + if (key in value) { + const parser = parserMap[key]; + const state = parser.enumParsed(a[key]); + if ("error" in state) { + const { error } = state; + error.keys.push(saferStringify(key)); + return onParse.invalid(error); + } + const smallValue = state.value; + value[key] = smallValue; + } else if (!isPartial) { + return onParse.invalid({ + value: "missingProperty", + parser, + keys: [ + saferStringify(key) + ] + }); + } + } + return onParse.parsed(value); + } +} +const isPartial = (testShape)=>{ + return new Parser(new ShapeParser(testShape, true)); +}; +const partial = isPartial; +class RecursiveParser { + recursive; + description; + parser; + static create(fn) { + const parser = new RecursiveParser(fn); + parser.parser = fn(new Parser(parser)); + return parser; + } + constructor(recursive, description = { + name: "Recursive", + children: [], + extras: [ + recursive + ] + }){ + this.recursive = recursive; + this.description = description; + } + parse(a, onParse) { + if (!this.parser) { + return onParse.invalid({ + value: "Recursive Invalid State", + keys: [], + parser: this + }); + } + return this.parser.parse(a, onParse); + } +} +function recursive(fn) { + fn(any); + const created = RecursiveParser.create(fn); + return new Parser(created); +} +const regex = (tester)=>string.refine(function(x) { + return tester.test(x); + }, tester.toString()); +const isShape = (testShape)=>{ + return new Parser(new ShapeParser(testShape, false)); +}; +function shape(testShape, optionals, optionalAndDefaults) { + if (optionals) { + const defaults = optionalAndDefaults || {}; + const entries = Object.entries(testShape); + const optionalSet = new Set(Array.from(optionals)); + return every(partial(Object.fromEntries(entries.filter(([key, _])=>optionalSet.has(key)).map(([key, parser])=>[ + key, + parser.optional() + ]))), isShape(Object.fromEntries(entries.filter(([key, _])=>!optionalSet.has(key))))).map((ret)=>{ + for (const key of optionalSet){ + const keyAny = key; + if (!(keyAny in ret) && keyAny in defaults) { + ret[keyAny] = defaults[keyAny]; + } + } + return ret; + }); + } + return isShape(testShape); +} +function some(...parsers) { + if (parsers.length <= 0) { + return any; + } + const first = parsers.splice(0, 1)[0]; + return parsers.reduce((left, right)=>left.orParser(right), first); +} +class TupleParser { + parsers; + lengthMatcher; + description; + constructor(parsers, lengthMatcher = literal(parsers.length), description = { + name: "Tuple", + children: parsers, + extras: [] + }){ + this.parsers = parsers; + this.lengthMatcher = lengthMatcher; + this.description = description; + } + parse(input, onParse) { + const tupleError = isArray.enumParsed(input); + if ("error" in tupleError) return onParse.invalid(tupleError.error); + const values = input; + const stateCheck = this.lengthMatcher.enumParsed(values.length); + if ("error" in stateCheck) { + stateCheck.error.keys.push(saferStringify("length")); + return onParse.invalid(stateCheck.error); + } + const answer = new Array(this.parsers.length); + for(const key in this.parsers){ + const parser = this.parsers[key]; + const value = values[key]; + const result = parser.enumParsed(value); + if ("error" in result) { + const { error } = result; + error.keys.push(saferStringify(key)); + return onParse.invalid(error); + } + answer[key] = result.value; + } + return onParse.parsed(answer); + } +} +function tuple(...parsers) { + return new Parser(new TupleParser(parsers)); +} +class NamedParser { + parent; + name; + description; + constructor(parent, name, description = { + name: "Named", + children: [ + parent + ], + extras: [ + name + ] + }){ + this.parent = parent; + this.name = name; + this.description = description; + } + parse(a, onParse) { + const parser = this; + const parent = this.parent.enumParsed(a); + if ("error" in parent) { + const { error } = parent; + error.parser = parser; + return onParse.invalid(error); + } + return onParse.parsed(parent.value); + } +} +function parserName(name, parent) { + return new Parser(new NamedParser(parent, name)); +} +class Matched { + value; + constructor(value){ + this.value = value; + } + when(..._args) { + return this; + } + defaultTo(_defaultValue) { + return this.value; + } + defaultToLazy(_getValue) { + return this.value; + } + unwrap() { + return this.value; + } +} +class MatchMore { + a; + constructor(a){ + this.a = a; + } + when(...args) { + const [outcome, ...matchers] = args.reverse(); + const me = this; + const parser = matches.some(...matchers.map((matcher)=>matcher instanceof Parser ? matcher : literal(matcher))); + const result = parser.enumParsed(this.a); + if ("error" in result) { + return me; + } + const { value } = result; + if (outcome instanceof Function) { + return new Matched(outcome(value)); + } + return new Matched(outcome); + } + defaultTo(value) { + return value; + } + defaultToLazy(getValue) { + return getValue(); + } + unwrap() { + throw new Error("Expecting that value is matched"); + } +} +const matches = Object.assign(function matchesFn(value) { + return new MatchMore(value); +}, { + array: isArray, + arrayOf, + some, + tuple, + regex, + number, + natural, + isFunction, + object, + string, + shape, + partial, + literal, + every, + guard, + unknown, + any, + boolean: __boolean, + dictionary, + literals, + nill: isNill, + instanceOf, + Parse: Parser, + parserName, + recursive, + deferred +}); +const mod = { + AnyParser: AnyParser, + ArrayParser: ArrayParser, + BoolParser: BoolParser, + FunctionParser: FunctionParser, + GuardParser: GuardParser, + NilParser: NilParser, + NumberParser: NumberParser, + ObjectParser: ObjectParser, + OrParsers: OrParsers, + ShapeParser: ShapeParser, + StringParser: StringParser, + saferStringify: saferStringify, + NamedParser: NamedParser, + ArrayOfParser: ArrayOfParser, + LiteralsParser: LiteralsParser, + ConcatParsers: ConcatParsers, + MappedAParser: MappedAParser, + default: matches, + Validator: Parser, + matches, + allOf: every, + any, + anyOf: some, + array: isArray, + arrayOf, + boolean: __boolean, + deferred, + dictionary, + every, + guard, + instanceOf, + isFunction, + literal, + literals, + natural, + nill: isNill, + number, + object, + oneOf: some, + Parse: Parser, + Parser, + parserName, + partial, + recursive, + regex, + shape, + some, + string, + tuple, + unknown +}; +class YAMLError extends Error { + mark; + constructor(message = "(unknown reason)", mark = ""){ + super(`${message} ${mark}`); + this.mark = mark; + this.name = this.constructor.name; + } + toString(_compact) { + return `${this.name}: ${this.message} ${this.mark}`; + } +} +function isBoolean(value) { + return typeof value === "boolean" || value instanceof Boolean; +} +function isObject1(value) { + return value !== null && typeof value === "object"; +} +function repeat(str, count) { + let result = ""; + for(let cycle = 0; cycle < count; cycle++){ + result += str; + } + return result; +} +function isNegativeZero(i) { + return i === 0 && Number.NEGATIVE_INFINITY === 1 / i; +} +class Mark { + name; + buffer; + position; + line; + column; + constructor(name, buffer, position, line, column){ + this.name = name; + this.buffer = buffer; + this.position = position; + this.line = line; + this.column = column; + } + getSnippet(indent = 4, maxLength = 75) { + if (!this.buffer) return null; + let head = ""; + let start = this.position; + while(start > 0 && "\x00\r\n\x85\u2028\u2029".indexOf(this.buffer.charAt(start - 1)) === -1){ + start -= 1; + if (this.position - start > maxLength / 2 - 1) { + head = " ... "; + start += 5; + break; + } + } + let tail = ""; + let end = this.position; + while(end < this.buffer.length && "\x00\r\n\x85\u2028\u2029".indexOf(this.buffer.charAt(end)) === -1){ + end += 1; + if (end - this.position > maxLength / 2 - 1) { + tail = " ... "; + end -= 5; + break; + } + } + const snippet = this.buffer.slice(start, end); + return `${repeat(" ", indent)}${head}${snippet}${tail}\n${repeat(" ", indent + this.position - start + head.length)}^`; + } + toString(compact) { + let snippet, where = ""; + if (this.name) { + where += `in "${this.name}" `; + } + where += `at line ${this.line + 1}, column ${this.column + 1}`; + if (!compact) { + snippet = this.getSnippet(); + if (snippet) { + where += `:\n${snippet}`; + } + } + return where; + } +} +function compileList(schema, name, result) { + const exclude = []; + for (const includedSchema of schema.include){ + result = compileList(includedSchema, name, result); + } + for (const currentType of schema[name]){ + for(let previousIndex = 0; previousIndex < result.length; previousIndex++){ + const previousType = result[previousIndex]; + if (previousType.tag === currentType.tag && previousType.kind === currentType.kind) { + exclude.push(previousIndex); + } + } + result.push(currentType); + } + return result.filter((_type, index)=>!exclude.includes(index)); +} +function compileMap(...typesList) { + const result = { + fallback: {}, + mapping: {}, + scalar: {}, + sequence: {} + }; + for (const types of typesList){ + for (const type of types){ + if (type.kind !== null) { + result[type.kind][type.tag] = result["fallback"][type.tag] = type; + } + } + } + return result; +} +class Schema { + static SCHEMA_DEFAULT; + implicit; + explicit; + include; + compiledImplicit; + compiledExplicit; + compiledTypeMap; + constructor(definition){ + this.explicit = definition.explicit || []; + this.implicit = definition.implicit || []; + this.include = definition.include || []; + for (const type of this.implicit){ + if (type.loadKind && type.loadKind !== "scalar") { + throw new YAMLError("There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported."); + } + } + this.compiledImplicit = compileList(this, "implicit", []); + this.compiledExplicit = compileList(this, "explicit", []); + this.compiledTypeMap = compileMap(this.compiledImplicit, this.compiledExplicit); + } + extend(definition) { + return new Schema({ + implicit: [ + ...new Set([ + ...this.implicit, + ...definition?.implicit ?? [] + ]) + ], + explicit: [ + ...new Set([ + ...this.explicit, + ...definition?.explicit ?? [] + ]) + ], + include: [ + ...new Set([ + ...this.include, + ...definition?.include ?? [] + ]) + ] + }); + } + static create() {} +} +const DEFAULT_RESOLVE = ()=>true; +const DEFAULT_CONSTRUCT = (data)=>data; +function checkTagFormat(tag) { + return tag; +} +class Type { + tag; + kind = null; + instanceOf; + predicate; + represent; + defaultStyle; + styleAliases; + loadKind; + constructor(tag, options){ + this.tag = checkTagFormat(tag); + if (options) { + this.kind = options.kind; + this.resolve = options.resolve || DEFAULT_RESOLVE; + this.construct = options.construct || DEFAULT_CONSTRUCT; + this.instanceOf = options.instanceOf; + this.predicate = options.predicate; + this.represent = options.represent; + this.defaultStyle = options.defaultStyle; + this.styleAliases = options.styleAliases; + } + } + resolve = ()=>true; + construct = (data)=>data; +} +class DenoStdInternalError extends Error { + constructor(message){ + super(message); + this.name = "DenoStdInternalError"; + } +} +function assert(expr, msg = "") { + if (!expr) { + throw new DenoStdInternalError(msg); + } +} +function copy(src, dst, off = 0) { + off = Math.max(0, Math.min(off, dst.byteLength)); + const dstBytesAvailable = dst.byteLength - off; + if (src.byteLength > dstBytesAvailable) { + src = src.subarray(0, dstBytesAvailable); + } + dst.set(src, off); + return src.byteLength; +} +const MIN_READ = 32 * 1024; +const MAX_SIZE = 2 ** 32 - 2; +class Buffer { + #buf; + #off = 0; + constructor(ab){ + this.#buf = ab === undefined ? new Uint8Array(0) : new Uint8Array(ab); + } + bytes(options = { + copy: true + }) { + if (options.copy === false) return this.#buf.subarray(this.#off); + return this.#buf.slice(this.#off); + } + empty() { + return this.#buf.byteLength <= this.#off; + } + get length() { + return this.#buf.byteLength - this.#off; + } + get capacity() { + return this.#buf.buffer.byteLength; + } + truncate(n) { + if (n === 0) { + this.reset(); + return; + } + if (n < 0 || n > this.length) { + throw Error("bytes.Buffer: truncation out of range"); + } + this.#reslice(this.#off + n); + } + reset() { + this.#reslice(0); + this.#off = 0; + } + #tryGrowByReslice(n) { + const l = this.#buf.byteLength; + if (n <= this.capacity - l) { + this.#reslice(l + n); + return l; + } + return -1; + } + #reslice(len) { + assert(len <= this.#buf.buffer.byteLength); + this.#buf = new Uint8Array(this.#buf.buffer, 0, len); + } + readSync(p) { + if (this.empty()) { + this.reset(); + if (p.byteLength === 0) { + return 0; + } + return null; + } + const nread = copy(this.#buf.subarray(this.#off), p); + this.#off += nread; + return nread; + } + read(p) { + const rr = this.readSync(p); + return Promise.resolve(rr); + } + writeSync(p) { + const m = this.#grow(p.byteLength); + return copy(p, this.#buf, m); + } + write(p) { + const n = this.writeSync(p); + return Promise.resolve(n); + } + #grow(n) { + const m = this.length; + if (m === 0 && this.#off !== 0) { + this.reset(); + } + const i = this.#tryGrowByReslice(n); + if (i >= 0) { + return i; + } + const c = this.capacity; + if (n <= Math.floor(c / 2) - m) { + copy(this.#buf.subarray(this.#off), this.#buf); + } else if (c + n > MAX_SIZE) { + throw new Error("The buffer cannot be grown beyond the maximum size."); + } else { + const buf = new Uint8Array(Math.min(2 * c + n, MAX_SIZE)); + copy(this.#buf.subarray(this.#off), buf); + this.#buf = buf; + } + this.#off = 0; + this.#reslice(Math.min(m + n, MAX_SIZE)); + return m; + } + grow(n) { + if (n < 0) { + throw Error("Buffer.grow: negative count"); + } + const m = this.#grow(n); + this.#reslice(m); + } + async readFrom(r) { + let n = 0; + const tmp = new Uint8Array(MIN_READ); + while(true){ + const shouldGrow = this.capacity - this.length < MIN_READ; + const buf = shouldGrow ? tmp : new Uint8Array(this.#buf.buffer, this.length); + const nread = await r.read(buf); + if (nread === null) { + return n; + } + if (shouldGrow) this.writeSync(buf.subarray(0, nread)); + else this.#reslice(this.length + nread); + n += nread; + } + } + readFromSync(r) { + let n = 0; + const tmp = new Uint8Array(MIN_READ); + while(true){ + const shouldGrow = this.capacity - this.length < MIN_READ; + const buf = shouldGrow ? tmp : new Uint8Array(this.#buf.buffer, this.length); + const nread = r.readSync(buf); + if (nread === null) { + return n; + } + if (shouldGrow) this.writeSync(buf.subarray(0, nread)); + else this.#reslice(this.length + nread); + n += nread; + } + } +} +const BASE64_MAP = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r"; +function resolveYamlBinary(data) { + if (data === null) return false; + let code; + let bitlen = 0; + const max = data.length; + const map = BASE64_MAP; + for(let idx = 0; idx < max; idx++){ + code = map.indexOf(data.charAt(idx)); + if (code > 64) continue; + if (code < 0) return false; + bitlen += 6; + } + return bitlen % 8 === 0; +} +function constructYamlBinary(data) { + const input = data.replace(/[\r\n=]/g, ""); + const max = input.length; + const map = BASE64_MAP; + const result = []; + let bits = 0; + for(let idx = 0; idx < max; idx++){ + if (idx % 4 === 0 && idx) { + result.push(bits >> 16 & 0xff); + result.push(bits >> 8 & 0xff); + result.push(bits & 0xff); + } + bits = bits << 6 | map.indexOf(input.charAt(idx)); + } + const tailbits = max % 4 * 6; + if (tailbits === 0) { + result.push(bits >> 16 & 0xff); + result.push(bits >> 8 & 0xff); + result.push(bits & 0xff); + } else if (tailbits === 18) { + result.push(bits >> 10 & 0xff); + result.push(bits >> 2 & 0xff); + } else if (tailbits === 12) { + result.push(bits >> 4 & 0xff); + } + return new Buffer(new Uint8Array(result)); +} +function representYamlBinary(object) { + const max = object.length; + const map = BASE64_MAP; + let result = ""; + let bits = 0; + for(let idx = 0; idx < max; idx++){ + if (idx % 3 === 0 && idx) { + result += map[bits >> 18 & 0x3f]; + result += map[bits >> 12 & 0x3f]; + result += map[bits >> 6 & 0x3f]; + result += map[bits & 0x3f]; + } + bits = (bits << 8) + object[idx]; + } + const tail = max % 3; + if (tail === 0) { + result += map[bits >> 18 & 0x3f]; + result += map[bits >> 12 & 0x3f]; + result += map[bits >> 6 & 0x3f]; + result += map[bits & 0x3f]; + } else if (tail === 2) { + result += map[bits >> 10 & 0x3f]; + result += map[bits >> 4 & 0x3f]; + result += map[bits << 2 & 0x3f]; + result += map[64]; + } else if (tail === 1) { + result += map[bits >> 2 & 0x3f]; + result += map[bits << 4 & 0x3f]; + result += map[64]; + result += map[64]; + } + return result; +} +function isBinary(obj) { + const buf = new Buffer(); + try { + if (0 > buf.readFromSync(obj)) return true; + return false; + } catch { + return false; + } finally{ + buf.reset(); + } +} +const binary = new Type("tag:yaml.org,2002:binary", { + construct: constructYamlBinary, + kind: "scalar", + predicate: isBinary, + represent: representYamlBinary, + resolve: resolveYamlBinary +}); +function resolveYamlBoolean(data) { + const max = data.length; + return max === 4 && (data === "true" || data === "True" || data === "TRUE") || max === 5 && (data === "false" || data === "False" || data === "FALSE"); +} +function constructYamlBoolean(data) { + return data === "true" || data === "True" || data === "TRUE"; +} +const bool = new Type("tag:yaml.org,2002:bool", { + construct: constructYamlBoolean, + defaultStyle: "lowercase", + kind: "scalar", + predicate: isBoolean, + represent: { + lowercase (object) { + return object ? "true" : "false"; + }, + uppercase (object) { + return object ? "TRUE" : "FALSE"; + }, + camelcase (object) { + return object ? "True" : "False"; + } + }, + resolve: resolveYamlBoolean +}); +const YAML_FLOAT_PATTERN = new RegExp("^(?:[-+]?(?:0|[1-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?" + "|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?" + "|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*" + "|[-+]?\\.(?:inf|Inf|INF)" + "|\\.(?:nan|NaN|NAN))$"); +function resolveYamlFloat(data) { + if (!YAML_FLOAT_PATTERN.test(data) || data[data.length - 1] === "_") { + return false; + } + return true; +} +function constructYamlFloat(data) { + let value = data.replace(/_/g, "").toLowerCase(); + const sign = value[0] === "-" ? -1 : 1; + const digits = []; + if ("+-".indexOf(value[0]) >= 0) { + value = value.slice(1); + } + if (value === ".inf") { + return sign === 1 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY; + } + if (value === ".nan") { + return NaN; + } + if (value.indexOf(":") >= 0) { + value.split(":").forEach((v)=>{ + digits.unshift(parseFloat(v)); + }); + let valueNb = 0.0; + let base = 1; + digits.forEach((d)=>{ + valueNb += d * base; + base *= 60; + }); + return sign * valueNb; + } + return sign * parseFloat(value); +} +const SCIENTIFIC_WITHOUT_DOT = /^[-+]?[0-9]+e/; +function representYamlFloat(object, style) { + if (isNaN(object)) { + switch(style){ + case "lowercase": + return ".nan"; + case "uppercase": + return ".NAN"; + case "camelcase": + return ".NaN"; + } + } else if (Number.POSITIVE_INFINITY === object) { + switch(style){ + case "lowercase": + return ".inf"; + case "uppercase": + return ".INF"; + case "camelcase": + return ".Inf"; + } + } else if (Number.NEGATIVE_INFINITY === object) { + switch(style){ + case "lowercase": + return "-.inf"; + case "uppercase": + return "-.INF"; + case "camelcase": + return "-.Inf"; + } + } else if (isNegativeZero(object)) { + return "-0.0"; + } + const res = object.toString(10); + return SCIENTIFIC_WITHOUT_DOT.test(res) ? res.replace("e", ".e") : res; +} +function isFloat(object) { + return Object.prototype.toString.call(object) === "[object Number]" && (object % 1 !== 0 || isNegativeZero(object)); +} +const __float = new Type("tag:yaml.org,2002:float", { + construct: constructYamlFloat, + defaultStyle: "lowercase", + kind: "scalar", + predicate: isFloat, + represent: representYamlFloat, + resolve: resolveYamlFloat +}); +function reconstructFunction(code) { + const func = new Function(`return ${code}`)(); + if (!(func instanceof Function)) { + throw new TypeError(`Expected function but got ${typeof func}: ${code}`); + } + return func; +} +new Type("tag:yaml.org,2002:js/function", { + kind: "scalar", + resolve (data) { + if (data === null) { + return false; + } + try { + reconstructFunction(`${data}`); + return true; + } catch (_err) { + return false; + } + }, + construct (data) { + return reconstructFunction(data); + }, + predicate (object) { + return object instanceof Function; + }, + represent (object) { + return object.toString(); + } +}); +function isHexCode(c) { + return 0x30 <= c && c <= 0x39 || 0x41 <= c && c <= 0x46 || 0x61 <= c && c <= 0x66; +} +function isOctCode(c) { + return 0x30 <= c && c <= 0x37; +} +function isDecCode(c) { + return 0x30 <= c && c <= 0x39; +} +function resolveYamlInteger(data) { + const max = data.length; + let index = 0; + let hasDigits = false; + if (!max) return false; + let ch = data[index]; + if (ch === "-" || ch === "+") { + ch = data[++index]; + } + if (ch === "0") { + if (index + 1 === max) return true; + ch = data[++index]; + if (ch === "b") { + index++; + for(; index < max; index++){ + ch = data[index]; + if (ch === "_") continue; + if (ch !== "0" && ch !== "1") return false; + hasDigits = true; + } + return hasDigits && ch !== "_"; + } + if (ch === "x") { + index++; + for(; index < max; index++){ + ch = data[index]; + if (ch === "_") continue; + if (!isHexCode(data.charCodeAt(index))) return false; + hasDigits = true; + } + return hasDigits && ch !== "_"; + } + for(; index < max; index++){ + ch = data[index]; + if (ch === "_") continue; + if (!isOctCode(data.charCodeAt(index))) return false; + hasDigits = true; + } + return hasDigits && ch !== "_"; + } + if (ch === "_") return false; + for(; index < max; index++){ + ch = data[index]; + if (ch === "_") continue; + if (ch === ":") break; + if (!isDecCode(data.charCodeAt(index))) { + return false; + } + hasDigits = true; + } + if (!hasDigits || ch === "_") return false; + if (ch !== ":") return true; + return /^(:[0-5]?[0-9])+$/.test(data.slice(index)); +} +function constructYamlInteger(data) { + let value = data; + const digits = []; + if (value.indexOf("_") !== -1) { + value = value.replace(/_/g, ""); + } + let sign = 1; + let ch = value[0]; + if (ch === "-" || ch === "+") { + if (ch === "-") sign = -1; + value = value.slice(1); + ch = value[0]; + } + if (value === "0") return 0; + if (ch === "0") { + if (value[1] === "b") return sign * parseInt(value.slice(2), 2); + if (value[1] === "x") return sign * parseInt(value, 16); + return sign * parseInt(value, 8); + } + if (value.indexOf(":") !== -1) { + value.split(":").forEach((v)=>{ + digits.unshift(parseInt(v, 10)); + }); + let valueInt = 0; + let base = 1; + digits.forEach((d)=>{ + valueInt += d * base; + base *= 60; + }); + return sign * valueInt; + } + return sign * parseInt(value, 10); +} +function isInteger(object) { + return Object.prototype.toString.call(object) === "[object Number]" && object % 1 === 0 && !isNegativeZero(object); +} +const __int = new Type("tag:yaml.org,2002:int", { + construct: constructYamlInteger, + defaultStyle: "decimal", + kind: "scalar", + predicate: isInteger, + represent: { + binary (obj) { + return obj >= 0 ? `0b${obj.toString(2)}` : `-0b${obj.toString(2).slice(1)}`; + }, + octal (obj) { + return obj >= 0 ? `0${obj.toString(8)}` : `-0${obj.toString(8).slice(1)}`; + }, + decimal (obj) { + return obj.toString(10); + }, + hexadecimal (obj) { + return obj >= 0 ? `0x${obj.toString(16).toUpperCase()}` : `-0x${obj.toString(16).toUpperCase().slice(1)}`; + } + }, + resolve: resolveYamlInteger, + styleAliases: { + binary: [ + 2, + "bin" + ], + decimal: [ + 10, + "dec" + ], + hexadecimal: [ + 16, + "hex" + ], + octal: [ + 8, + "oct" + ] + } +}); +const map = new Type("tag:yaml.org,2002:map", { + construct (data) { + return data !== null ? data : {}; + }, + kind: "mapping" +}); +function resolveYamlMerge(data) { + return data === "<<" || data === null; +} +const merge = new Type("tag:yaml.org,2002:merge", { + kind: "scalar", + resolve: resolveYamlMerge +}); +function resolveYamlNull(data) { + const max = data.length; + return max === 1 && data === "~" || max === 4 && (data === "null" || data === "Null" || data === "NULL"); +} +function constructYamlNull() { + return null; +} +function isNull(object) { + return object === null; +} +const nil = new Type("tag:yaml.org,2002:null", { + construct: constructYamlNull, + defaultStyle: "lowercase", + kind: "scalar", + predicate: isNull, + represent: { + canonical () { + return "~"; + }, + lowercase () { + return "null"; + }, + uppercase () { + return "NULL"; + }, + camelcase () { + return "Null"; + } + }, + resolve: resolveYamlNull +}); +const { hasOwn } = Object; +const _toString = Object.prototype.toString; +function resolveYamlOmap(data) { + const objectKeys = []; + let pairKey = ""; + let pairHasKey = false; + for (const pair of data){ + pairHasKey = false; + if (_toString.call(pair) !== "[object Object]") return false; + for(pairKey in pair){ + if (hasOwn(pair, pairKey)) { + if (!pairHasKey) pairHasKey = true; + else return false; + } + } + if (!pairHasKey) return false; + if (objectKeys.indexOf(pairKey) === -1) objectKeys.push(pairKey); + else return false; + } + return true; +} +function constructYamlOmap(data) { + return data !== null ? data : []; +} +const omap = new Type("tag:yaml.org,2002:omap", { + construct: constructYamlOmap, + kind: "sequence", + resolve: resolveYamlOmap +}); +const _toString1 = Object.prototype.toString; +function resolveYamlPairs(data) { + const result = Array.from({ + length: data.length + }); + for(let index = 0; index < data.length; index++){ + const pair = data[index]; + if (_toString1.call(pair) !== "[object Object]") return false; + const keys = Object.keys(pair); + if (keys.length !== 1) return false; + result[index] = [ + keys[0], + pair[keys[0]] + ]; + } + return true; +} +function constructYamlPairs(data) { + if (data === null) return []; + const result = Array.from({ + length: data.length + }); + for(let index = 0; index < data.length; index += 1){ + const pair = data[index]; + const keys = Object.keys(pair); + result[index] = [ + keys[0], + pair[keys[0]] + ]; + } + return result; +} +const pairs = new Type("tag:yaml.org,2002:pairs", { + construct: constructYamlPairs, + kind: "sequence", + resolve: resolveYamlPairs +}); +const REGEXP = /^\/(?[\s\S]+)\/(?[gismuy]*)$/; +const regexp = new Type("tag:yaml.org,2002:js/regexp", { + kind: "scalar", + resolve (data) { + if (data === null || !data.length) { + return false; + } + const regexp = `${data}`; + if (regexp.charAt(0) === "/") { + if (!REGEXP.test(data)) { + return false; + } + const modifiers = [ + ...regexp.match(REGEXP)?.groups?.modifiers ?? "" + ]; + if (new Set(modifiers).size < modifiers.length) { + return false; + } + } + return true; + }, + construct (data) { + const { regexp = `${data}`, modifiers = "" } = `${data}`.match(REGEXP)?.groups ?? {}; + return new RegExp(regexp, modifiers); + }, + predicate (object) { + return object instanceof RegExp; + }, + represent (object) { + return object.toString(); + } +}); +const seq = new Type("tag:yaml.org,2002:seq", { + construct (data) { + return data !== null ? data : []; + }, + kind: "sequence" +}); +const { hasOwn: hasOwn1 } = Object; +function resolveYamlSet(data) { + if (data === null) return true; + for(const key in data){ + if (hasOwn1(data, key)) { + if (data[key] !== null) return false; + } + } + return true; +} +function constructYamlSet(data) { + return data !== null ? data : {}; +} +const set = new Type("tag:yaml.org,2002:set", { + construct: constructYamlSet, + kind: "mapping", + resolve: resolveYamlSet +}); +const str = new Type("tag:yaml.org,2002:str", { + construct (data) { + return data !== null ? data : ""; + }, + kind: "scalar" +}); +const YAML_DATE_REGEXP = new RegExp("^([0-9][0-9][0-9][0-9])" + "-([0-9][0-9])" + "-([0-9][0-9])$"); +const YAML_TIMESTAMP_REGEXP = new RegExp("^([0-9][0-9][0-9][0-9])" + "-([0-9][0-9]?)" + "-([0-9][0-9]?)" + "(?:[Tt]|[ \\t]+)" + "([0-9][0-9]?)" + ":([0-9][0-9])" + ":([0-9][0-9])" + "(?:\\.([0-9]*))?" + "(?:[ \\t]*(Z|([-+])([0-9][0-9]?)" + "(?::([0-9][0-9]))?))?$"); +function resolveYamlTimestamp(data) { + if (data === null) return false; + if (YAML_DATE_REGEXP.exec(data) !== null) return true; + if (YAML_TIMESTAMP_REGEXP.exec(data) !== null) return true; + return false; +} +function constructYamlTimestamp(data) { + let match = YAML_DATE_REGEXP.exec(data); + if (match === null) match = YAML_TIMESTAMP_REGEXP.exec(data); + if (match === null) throw new Error("Date resolve error"); + const year = +match[1]; + const month = +match[2] - 1; + const day = +match[3]; + if (!match[4]) { + return new Date(Date.UTC(year, month, day)); + } + const hour = +match[4]; + const minute = +match[5]; + const second = +match[6]; + let fraction = 0; + if (match[7]) { + let partFraction = match[7].slice(0, 3); + while(partFraction.length < 3){ + partFraction += "0"; + } + fraction = +partFraction; + } + let delta = null; + if (match[9]) { + const tzHour = +match[10]; + const tzMinute = +(match[11] || 0); + delta = (tzHour * 60 + tzMinute) * 60000; + if (match[9] === "-") delta = -delta; + } + const date = new Date(Date.UTC(year, month, day, hour, minute, second, fraction)); + if (delta) date.setTime(date.getTime() - delta); + return date; +} +function representYamlTimestamp(date) { + return date.toISOString(); +} +const timestamp = new Type("tag:yaml.org,2002:timestamp", { + construct: constructYamlTimestamp, + instanceOf: Date, + kind: "scalar", + represent: representYamlTimestamp, + resolve: resolveYamlTimestamp +}); +const undefinedType = new Type("tag:yaml.org,2002:js/undefined", { + kind: "scalar", + resolve () { + return true; + }, + construct () { + return undefined; + }, + predicate (object) { + return typeof object === "undefined"; + }, + represent () { + return ""; + } +}); +const failsafe = new Schema({ + explicit: [ + str, + seq, + map + ] +}); +const json = new Schema({ + implicit: [ + nil, + bool, + __int, + __float + ], + include: [ + failsafe + ] +}); +const core = new Schema({ + include: [ + json + ] +}); +const def = new Schema({ + explicit: [ + binary, + omap, + pairs, + set + ], + implicit: [ + timestamp, + merge + ], + include: [ + core + ] +}); +const extended = new Schema({ + explicit: [ + regexp, + undefinedType + ], + include: [ + def + ] +}); +class State { + schema; + constructor(schema = def){ + this.schema = schema; + } +} +class LoaderState extends State { + input; + documents; + length; + lineIndent; + lineStart; + position; + line; + filename; + onWarning; + legacy; + json; + listener; + implicitTypes; + typeMap; + version; + checkLineBreaks; + tagMap; + anchorMap; + tag; + anchor; + kind; + result; + constructor(input, { filename, schema, onWarning, legacy = false, json = false, listener = null }){ + super(schema); + this.input = input; + this.documents = []; + this.lineIndent = 0; + this.lineStart = 0; + this.position = 0; + this.line = 0; + this.result = ""; + this.filename = filename; + this.onWarning = onWarning; + this.legacy = legacy; + this.json = json; + this.listener = listener; + this.implicitTypes = this.schema.compiledImplicit; + this.typeMap = this.schema.compiledTypeMap; + this.length = input.length; + } +} +const { hasOwn: hasOwn2 } = Object; +const CONTEXT_BLOCK_IN = 3; +const CONTEXT_BLOCK_OUT = 4; +const CHOMPING_STRIP = 2; +const CHOMPING_KEEP = 3; +const PATTERN_NON_PRINTABLE = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/; +const PATTERN_NON_ASCII_LINE_BREAKS = /[\x85\u2028\u2029]/; +const PATTERN_FLOW_INDICATORS = /[,\[\]\{\}]/; +const PATTERN_TAG_HANDLE = /^(?:!|!!|![a-z\-]+!)$/i; +const PATTERN_TAG_URI = /^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/i; +function _class(obj) { + return Object.prototype.toString.call(obj); +} +function isEOL(c) { + return c === 0x0a || c === 0x0d; +} +function isWhiteSpace(c) { + return c === 0x09 || c === 0x20; +} +function isWsOrEol(c) { + return c === 0x09 || c === 0x20 || c === 0x0a || c === 0x0d; +} +function isFlowIndicator(c) { + return c === 0x2c || c === 0x5b || c === 0x5d || c === 0x7b || c === 0x7d; +} +function fromHexCode(c) { + if (0x30 <= c && c <= 0x39) { + return c - 0x30; + } + const lc = c | 0x20; + if (0x61 <= lc && lc <= 0x66) { + return lc - 0x61 + 10; + } + return -1; +} +function escapedHexLen(c) { + if (c === 0x78) { + return 2; + } + if (c === 0x75) { + return 4; + } + if (c === 0x55) { + return 8; + } + return 0; +} +function fromDecimalCode(c) { + if (0x30 <= c && c <= 0x39) { + return c - 0x30; + } + return -1; +} +function simpleEscapeSequence(c) { + return c === 0x30 ? "\x00" : c === 0x61 ? "\x07" : c === 0x62 ? "\x08" : c === 0x74 ? "\x09" : c === 0x09 ? "\x09" : c === 0x6e ? "\x0A" : c === 0x76 ? "\x0B" : c === 0x66 ? "\x0C" : c === 0x72 ? "\x0D" : c === 0x65 ? "\x1B" : c === 0x20 ? " " : c === 0x22 ? "\x22" : c === 0x2f ? "/" : c === 0x5c ? "\x5C" : c === 0x4e ? "\x85" : c === 0x5f ? "\xA0" : c === 0x4c ? "\u2028" : c === 0x50 ? "\u2029" : ""; +} +function charFromCodepoint(c) { + if (c <= 0xffff) { + return String.fromCharCode(c); + } + return String.fromCharCode((c - 0x010000 >> 10) + 0xd800, (c - 0x010000 & 0x03ff) + 0xdc00); +} +const simpleEscapeCheck = Array.from({ + length: 256 +}); +const simpleEscapeMap = Array.from({ + length: 256 +}); +for(let i = 0; i < 256; i++){ + simpleEscapeCheck[i] = simpleEscapeSequence(i) ? 1 : 0; + simpleEscapeMap[i] = simpleEscapeSequence(i); +} +function generateError(state, message) { + return new YAMLError(message, new Mark(state.filename, state.input, state.position, state.line, state.position - state.lineStart)); +} +function throwError(state, message) { + throw generateError(state, message); +} +function throwWarning(state, message) { + if (state.onWarning) { + state.onWarning.call(null, generateError(state, message)); + } +} +const directiveHandlers = { + YAML (state, _name, ...args) { + if (state.version !== null) { + return throwError(state, "duplication of %YAML directive"); + } + if (args.length !== 1) { + return throwError(state, "YAML directive accepts exactly one argument"); + } + const match = /^([0-9]+)\.([0-9]+)$/.exec(args[0]); + if (match === null) { + return throwError(state, "ill-formed argument of the YAML directive"); + } + const major = parseInt(match[1], 10); + const minor = parseInt(match[2], 10); + if (major !== 1) { + return throwError(state, "unacceptable YAML version of the document"); + } + state.version = args[0]; + state.checkLineBreaks = minor < 2; + if (minor !== 1 && minor !== 2) { + return throwWarning(state, "unsupported YAML version of the document"); + } + }, + TAG (state, _name, ...args) { + if (args.length !== 2) { + return throwError(state, "TAG directive accepts exactly two arguments"); + } + const handle = args[0]; + const prefix = args[1]; + if (!PATTERN_TAG_HANDLE.test(handle)) { + return throwError(state, "ill-formed tag handle (first argument) of the TAG directive"); + } + if (state.tagMap && hasOwn2(state.tagMap, handle)) { + return throwError(state, `there is a previously declared suffix for "${handle}" tag handle`); + } + if (!PATTERN_TAG_URI.test(prefix)) { + return throwError(state, "ill-formed tag prefix (second argument) of the TAG directive"); + } + if (typeof state.tagMap === "undefined") { + state.tagMap = {}; + } + state.tagMap[handle] = prefix; + } +}; +function captureSegment(state, start, end, checkJson) { + let result; + if (start < end) { + result = state.input.slice(start, end); + if (checkJson) { + for(let position = 0, length = result.length; position < length; position++){ + const character = result.charCodeAt(position); + if (!(character === 0x09 || 0x20 <= character && character <= 0x10ffff)) { + return throwError(state, "expected valid JSON character"); + } + } + } else if (PATTERN_NON_PRINTABLE.test(result)) { + return throwError(state, "the stream contains non-printable characters"); + } + state.result += result; + } +} +function mergeMappings(state, destination, source, overridableKeys) { + if (!isObject1(source)) { + return throwError(state, "cannot merge mappings; the provided source object is unacceptable"); + } + const keys = Object.keys(source); + for(let i = 0, len = keys.length; i < len; i++){ + const key = keys[i]; + if (!hasOwn2(destination, key)) { + destination[key] = source[key]; + overridableKeys[key] = true; + } + } +} +function storeMappingPair(state, result, overridableKeys, keyTag, keyNode, valueNode, startLine, startPos) { + if (Array.isArray(keyNode)) { + keyNode = Array.prototype.slice.call(keyNode); + for(let index = 0, quantity = keyNode.length; index < quantity; index++){ + if (Array.isArray(keyNode[index])) { + return throwError(state, "nested arrays are not supported inside keys"); + } + if (typeof keyNode === "object" && _class(keyNode[index]) === "[object Object]") { + keyNode[index] = "[object Object]"; + } + } + } + if (typeof keyNode === "object" && _class(keyNode) === "[object Object]") { + keyNode = "[object Object]"; + } + keyNode = String(keyNode); + if (result === null) { + result = {}; + } + if (keyTag === "tag:yaml.org,2002:merge") { + if (Array.isArray(valueNode)) { + for(let index = 0, quantity = valueNode.length; index < quantity; index++){ + mergeMappings(state, result, valueNode[index], overridableKeys); + } + } else { + mergeMappings(state, result, valueNode, overridableKeys); + } + } else { + if (!state.json && !hasOwn2(overridableKeys, keyNode) && hasOwn2(result, keyNode)) { + state.line = startLine || state.line; + state.position = startPos || state.position; + return throwError(state, "duplicated mapping key"); + } + result[keyNode] = valueNode; + delete overridableKeys[keyNode]; + } + return result; +} +function readLineBreak(state) { + const ch = state.input.charCodeAt(state.position); + if (ch === 0x0a) { + state.position++; + } else if (ch === 0x0d) { + state.position++; + if (state.input.charCodeAt(state.position) === 0x0a) { + state.position++; + } + } else { + return throwError(state, "a line break is expected"); + } + state.line += 1; + state.lineStart = state.position; +} +function skipSeparationSpace(state, allowComments, checkIndent) { + let lineBreaks = 0, ch = state.input.charCodeAt(state.position); + while(ch !== 0){ + while(isWhiteSpace(ch)){ + ch = state.input.charCodeAt(++state.position); + } + if (allowComments && ch === 0x23) { + do { + ch = state.input.charCodeAt(++state.position); + }while (ch !== 0x0a && ch !== 0x0d && ch !== 0) + } + if (isEOL(ch)) { + readLineBreak(state); + ch = state.input.charCodeAt(state.position); + lineBreaks++; + state.lineIndent = 0; + while(ch === 0x20){ + state.lineIndent++; + ch = state.input.charCodeAt(++state.position); + } + } else { + break; + } + } + if (checkIndent !== -1 && lineBreaks !== 0 && state.lineIndent < checkIndent) { + throwWarning(state, "deficient indentation"); + } + return lineBreaks; +} +function testDocumentSeparator(state) { + let _position = state.position; + let ch = state.input.charCodeAt(_position); + if ((ch === 0x2d || ch === 0x2e) && ch === state.input.charCodeAt(_position + 1) && ch === state.input.charCodeAt(_position + 2)) { + _position += 3; + ch = state.input.charCodeAt(_position); + if (ch === 0 || isWsOrEol(ch)) { + return true; + } + } + return false; +} +function writeFoldedLines(state, count) { + if (count === 1) { + state.result += " "; + } else if (count > 1) { + state.result += repeat("\n", count - 1); + } +} +function readPlainScalar(state, nodeIndent, withinFlowCollection) { + const kind = state.kind; + const result = state.result; + let ch = state.input.charCodeAt(state.position); + if (isWsOrEol(ch) || isFlowIndicator(ch) || ch === 0x23 || ch === 0x26 || ch === 0x2a || ch === 0x21 || ch === 0x7c || ch === 0x3e || ch === 0x27 || ch === 0x22 || ch === 0x25 || ch === 0x40 || ch === 0x60) { + return false; + } + let following; + if (ch === 0x3f || ch === 0x2d) { + following = state.input.charCodeAt(state.position + 1); + if (isWsOrEol(following) || withinFlowCollection && isFlowIndicator(following)) { + return false; + } + } + state.kind = "scalar"; + state.result = ""; + let captureEnd, captureStart = captureEnd = state.position; + let hasPendingContent = false; + let line = 0; + while(ch !== 0){ + if (ch === 0x3a) { + following = state.input.charCodeAt(state.position + 1); + if (isWsOrEol(following) || withinFlowCollection && isFlowIndicator(following)) { + break; + } + } else if (ch === 0x23) { + const preceding = state.input.charCodeAt(state.position - 1); + if (isWsOrEol(preceding)) { + break; + } + } else if (state.position === state.lineStart && testDocumentSeparator(state) || withinFlowCollection && isFlowIndicator(ch)) { + break; + } else if (isEOL(ch)) { + line = state.line; + const lineStart = state.lineStart; + const lineIndent = state.lineIndent; + skipSeparationSpace(state, false, -1); + if (state.lineIndent >= nodeIndent) { + hasPendingContent = true; + ch = state.input.charCodeAt(state.position); + continue; + } else { + state.position = captureEnd; + state.line = line; + state.lineStart = lineStart; + state.lineIndent = lineIndent; + break; + } + } + if (hasPendingContent) { + captureSegment(state, captureStart, captureEnd, false); + writeFoldedLines(state, state.line - line); + captureStart = captureEnd = state.position; + hasPendingContent = false; + } + if (!isWhiteSpace(ch)) { + captureEnd = state.position + 1; + } + ch = state.input.charCodeAt(++state.position); + } + captureSegment(state, captureStart, captureEnd, false); + if (state.result) { + return true; + } + state.kind = kind; + state.result = result; + return false; +} +function readSingleQuotedScalar(state, nodeIndent) { + let ch, captureStart, captureEnd; + ch = state.input.charCodeAt(state.position); + if (ch !== 0x27) { + return false; + } + state.kind = "scalar"; + state.result = ""; + state.position++; + captureStart = captureEnd = state.position; + while((ch = state.input.charCodeAt(state.position)) !== 0){ + if (ch === 0x27) { + captureSegment(state, captureStart, state.position, true); + ch = state.input.charCodeAt(++state.position); + if (ch === 0x27) { + captureStart = state.position; + state.position++; + captureEnd = state.position; + } else { + return true; + } + } else if (isEOL(ch)) { + captureSegment(state, captureStart, captureEnd, true); + writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent)); + captureStart = captureEnd = state.position; + } else if (state.position === state.lineStart && testDocumentSeparator(state)) { + return throwError(state, "unexpected end of the document within a single quoted scalar"); + } else { + state.position++; + captureEnd = state.position; + } + } + return throwError(state, "unexpected end of the stream within a single quoted scalar"); +} +function readDoubleQuotedScalar(state, nodeIndent) { + let ch = state.input.charCodeAt(state.position); + if (ch !== 0x22) { + return false; + } + state.kind = "scalar"; + state.result = ""; + state.position++; + let captureEnd, captureStart = captureEnd = state.position; + let tmp; + while((ch = state.input.charCodeAt(state.position)) !== 0){ + if (ch === 0x22) { + captureSegment(state, captureStart, state.position, true); + state.position++; + return true; + } + if (ch === 0x5c) { + captureSegment(state, captureStart, state.position, true); + ch = state.input.charCodeAt(++state.position); + if (isEOL(ch)) { + skipSeparationSpace(state, false, nodeIndent); + } else if (ch < 256 && simpleEscapeCheck[ch]) { + state.result += simpleEscapeMap[ch]; + state.position++; + } else if ((tmp = escapedHexLen(ch)) > 0) { + let hexLength = tmp; + let hexResult = 0; + for(; hexLength > 0; hexLength--){ + ch = state.input.charCodeAt(++state.position); + if ((tmp = fromHexCode(ch)) >= 0) { + hexResult = (hexResult << 4) + tmp; + } else { + return throwError(state, "expected hexadecimal character"); + } + } + state.result += charFromCodepoint(hexResult); + state.position++; + } else { + return throwError(state, "unknown escape sequence"); + } + captureStart = captureEnd = state.position; + } else if (isEOL(ch)) { + captureSegment(state, captureStart, captureEnd, true); + writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent)); + captureStart = captureEnd = state.position; + } else if (state.position === state.lineStart && testDocumentSeparator(state)) { + return throwError(state, "unexpected end of the document within a double quoted scalar"); + } else { + state.position++; + captureEnd = state.position; + } + } + return throwError(state, "unexpected end of the stream within a double quoted scalar"); +} +function readFlowCollection(state, nodeIndent) { + let ch = state.input.charCodeAt(state.position); + let terminator; + let isMapping = true; + let result = {}; + if (ch === 0x5b) { + terminator = 0x5d; + isMapping = false; + result = []; + } else if (ch === 0x7b) { + terminator = 0x7d; + } else { + return false; + } + if (state.anchor !== null && typeof state.anchor != "undefined" && typeof state.anchorMap != "undefined") { + state.anchorMap[state.anchor] = result; + } + ch = state.input.charCodeAt(++state.position); + const tag = state.tag, anchor = state.anchor; + let readNext = true; + let valueNode, keyNode, keyTag = keyNode = valueNode = null, isExplicitPair, isPair = isExplicitPair = false; + let following = 0, line = 0; + const overridableKeys = {}; + while(ch !== 0){ + skipSeparationSpace(state, true, nodeIndent); + ch = state.input.charCodeAt(state.position); + if (ch === terminator) { + state.position++; + state.tag = tag; + state.anchor = anchor; + state.kind = isMapping ? "mapping" : "sequence"; + state.result = result; + return true; + } + if (!readNext) { + return throwError(state, "missed comma between flow collection entries"); + } + keyTag = keyNode = valueNode = null; + isPair = isExplicitPair = false; + if (ch === 0x3f) { + following = state.input.charCodeAt(state.position + 1); + if (isWsOrEol(following)) { + isPair = isExplicitPair = true; + state.position++; + skipSeparationSpace(state, true, nodeIndent); + } + } + line = state.line; + composeNode(state, nodeIndent, 1, false, true); + keyTag = state.tag || null; + keyNode = state.result; + skipSeparationSpace(state, true, nodeIndent); + ch = state.input.charCodeAt(state.position); + if ((isExplicitPair || state.line === line) && ch === 0x3a) { + isPair = true; + ch = state.input.charCodeAt(++state.position); + skipSeparationSpace(state, true, nodeIndent); + composeNode(state, nodeIndent, 1, false, true); + valueNode = state.result; + } + if (isMapping) { + storeMappingPair(state, result, overridableKeys, keyTag, keyNode, valueNode); + } else if (isPair) { + result.push(storeMappingPair(state, null, overridableKeys, keyTag, keyNode, valueNode)); + } else { + result.push(keyNode); + } + skipSeparationSpace(state, true, nodeIndent); + ch = state.input.charCodeAt(state.position); + if (ch === 0x2c) { + readNext = true; + ch = state.input.charCodeAt(++state.position); + } else { + readNext = false; + } + } + return throwError(state, "unexpected end of the stream within a flow collection"); +} +function readBlockScalar(state, nodeIndent) { + let chomping = 1, didReadContent = false, detectedIndent = false, textIndent = nodeIndent, emptyLines = 0, atMoreIndented = false; + let ch = state.input.charCodeAt(state.position); + let folding = false; + if (ch === 0x7c) { + folding = false; + } else if (ch === 0x3e) { + folding = true; + } else { + return false; + } + state.kind = "scalar"; + state.result = ""; + let tmp = 0; + while(ch !== 0){ + ch = state.input.charCodeAt(++state.position); + if (ch === 0x2b || ch === 0x2d) { + if (1 === chomping) { + chomping = ch === 0x2b ? CHOMPING_KEEP : CHOMPING_STRIP; + } else { + return throwError(state, "repeat of a chomping mode identifier"); + } + } else if ((tmp = fromDecimalCode(ch)) >= 0) { + if (tmp === 0) { + return throwError(state, "bad explicit indentation width of a block scalar; it cannot be less than one"); + } else if (!detectedIndent) { + textIndent = nodeIndent + tmp - 1; + detectedIndent = true; + } else { + return throwError(state, "repeat of an indentation width identifier"); + } + } else { + break; + } + } + if (isWhiteSpace(ch)) { + do { + ch = state.input.charCodeAt(++state.position); + }while (isWhiteSpace(ch)) + if (ch === 0x23) { + do { + ch = state.input.charCodeAt(++state.position); + }while (!isEOL(ch) && ch !== 0) + } + } + while(ch !== 0){ + readLineBreak(state); + state.lineIndent = 0; + ch = state.input.charCodeAt(state.position); + while((!detectedIndent || state.lineIndent < textIndent) && ch === 0x20){ + state.lineIndent++; + ch = state.input.charCodeAt(++state.position); + } + if (!detectedIndent && state.lineIndent > textIndent) { + textIndent = state.lineIndent; + } + if (isEOL(ch)) { + emptyLines++; + continue; + } + if (state.lineIndent < textIndent) { + if (chomping === 3) { + state.result += repeat("\n", didReadContent ? 1 + emptyLines : emptyLines); + } else if (chomping === 1) { + if (didReadContent) { + state.result += "\n"; + } + } + break; + } + if (folding) { + if (isWhiteSpace(ch)) { + atMoreIndented = true; + state.result += repeat("\n", didReadContent ? 1 + emptyLines : emptyLines); + } else if (atMoreIndented) { + atMoreIndented = false; + state.result += repeat("\n", emptyLines + 1); + } else if (emptyLines === 0) { + if (didReadContent) { + state.result += " "; + } + } else { + state.result += repeat("\n", emptyLines); + } + } else { + state.result += repeat("\n", didReadContent ? 1 + emptyLines : emptyLines); + } + didReadContent = true; + detectedIndent = true; + emptyLines = 0; + const captureStart = state.position; + while(!isEOL(ch) && ch !== 0){ + ch = state.input.charCodeAt(++state.position); + } + captureSegment(state, captureStart, state.position, false); + } + return true; +} +function readBlockSequence(state, nodeIndent) { + let line, following, detected = false, ch; + const tag = state.tag, anchor = state.anchor, result = []; + if (state.anchor !== null && typeof state.anchor !== "undefined" && typeof state.anchorMap !== "undefined") { + state.anchorMap[state.anchor] = result; + } + ch = state.input.charCodeAt(state.position); + while(ch !== 0){ + if (ch !== 0x2d) { + break; + } + following = state.input.charCodeAt(state.position + 1); + if (!isWsOrEol(following)) { + break; + } + detected = true; + state.position++; + if (skipSeparationSpace(state, true, -1)) { + if (state.lineIndent <= nodeIndent) { + result.push(null); + ch = state.input.charCodeAt(state.position); + continue; + } + } + line = state.line; + composeNode(state, nodeIndent, 3, false, true); + result.push(state.result); + skipSeparationSpace(state, true, -1); + ch = state.input.charCodeAt(state.position); + if ((state.line === line || state.lineIndent > nodeIndent) && ch !== 0) { + return throwError(state, "bad indentation of a sequence entry"); + } else if (state.lineIndent < nodeIndent) { + break; + } + } + if (detected) { + state.tag = tag; + state.anchor = anchor; + state.kind = "sequence"; + state.result = result; + return true; + } + return false; +} +function readBlockMapping(state, nodeIndent, flowIndent) { + const tag = state.tag, anchor = state.anchor, result = {}, overridableKeys = {}; + let following, allowCompact = false, line, pos, keyTag = null, keyNode = null, valueNode = null, atExplicitKey = false, detected = false, ch; + if (state.anchor !== null && typeof state.anchor !== "undefined" && typeof state.anchorMap !== "undefined") { + state.anchorMap[state.anchor] = result; + } + ch = state.input.charCodeAt(state.position); + while(ch !== 0){ + following = state.input.charCodeAt(state.position + 1); + line = state.line; + pos = state.position; + if ((ch === 0x3f || ch === 0x3a) && isWsOrEol(following)) { + if (ch === 0x3f) { + if (atExplicitKey) { + storeMappingPair(state, result, overridableKeys, keyTag, keyNode, null); + keyTag = keyNode = valueNode = null; + } + detected = true; + atExplicitKey = true; + allowCompact = true; + } else if (atExplicitKey) { + atExplicitKey = false; + allowCompact = true; + } else { + return throwError(state, "incomplete explicit mapping pair; a key node is missed; or followed by a non-tabulated empty line"); + } + state.position += 1; + ch = following; + } else if (composeNode(state, flowIndent, 2, false, true)) { + if (state.line === line) { + ch = state.input.charCodeAt(state.position); + while(isWhiteSpace(ch)){ + ch = state.input.charCodeAt(++state.position); + } + if (ch === 0x3a) { + ch = state.input.charCodeAt(++state.position); + if (!isWsOrEol(ch)) { + return throwError(state, "a whitespace character is expected after the key-value separator within a block mapping"); + } + if (atExplicitKey) { + storeMappingPair(state, result, overridableKeys, keyTag, keyNode, null); + keyTag = keyNode = valueNode = null; + } + detected = true; + atExplicitKey = false; + allowCompact = false; + keyTag = state.tag; + keyNode = state.result; + } else if (detected) { + return throwError(state, "can not read an implicit mapping pair; a colon is missed"); + } else { + state.tag = tag; + state.anchor = anchor; + return true; + } + } else if (detected) { + return throwError(state, "can not read a block mapping entry; a multiline key may not be an implicit key"); + } else { + state.tag = tag; + state.anchor = anchor; + return true; + } + } else { + break; + } + if (state.line === line || state.lineIndent > nodeIndent) { + if (composeNode(state, nodeIndent, 4, true, allowCompact)) { + if (atExplicitKey) { + keyNode = state.result; + } else { + valueNode = state.result; + } + } + if (!atExplicitKey) { + storeMappingPair(state, result, overridableKeys, keyTag, keyNode, valueNode, line, pos); + keyTag = keyNode = valueNode = null; + } + skipSeparationSpace(state, true, -1); + ch = state.input.charCodeAt(state.position); + } + if (state.lineIndent > nodeIndent && ch !== 0) { + return throwError(state, "bad indentation of a mapping entry"); + } else if (state.lineIndent < nodeIndent) { + break; + } + } + if (atExplicitKey) { + storeMappingPair(state, result, overridableKeys, keyTag, keyNode, null); + } + if (detected) { + state.tag = tag; + state.anchor = anchor; + state.kind = "mapping"; + state.result = result; + } + return detected; +} +function readTagProperty(state) { + let position, isVerbatim = false, isNamed = false, tagHandle = "", tagName, ch; + ch = state.input.charCodeAt(state.position); + if (ch !== 0x21) return false; + if (state.tag !== null) { + return throwError(state, "duplication of a tag property"); + } + ch = state.input.charCodeAt(++state.position); + if (ch === 0x3c) { + isVerbatim = true; + ch = state.input.charCodeAt(++state.position); + } else if (ch === 0x21) { + isNamed = true; + tagHandle = "!!"; + ch = state.input.charCodeAt(++state.position); + } else { + tagHandle = "!"; + } + position = state.position; + if (isVerbatim) { + do { + ch = state.input.charCodeAt(++state.position); + }while (ch !== 0 && ch !== 0x3e) + if (state.position < state.length) { + tagName = state.input.slice(position, state.position); + ch = state.input.charCodeAt(++state.position); + } else { + return throwError(state, "unexpected end of the stream within a verbatim tag"); + } + } else { + while(ch !== 0 && !isWsOrEol(ch)){ + if (ch === 0x21) { + if (!isNamed) { + tagHandle = state.input.slice(position - 1, state.position + 1); + if (!PATTERN_TAG_HANDLE.test(tagHandle)) { + return throwError(state, "named tag handle cannot contain such characters"); + } + isNamed = true; + position = state.position + 1; + } else { + return throwError(state, "tag suffix cannot contain exclamation marks"); + } + } + ch = state.input.charCodeAt(++state.position); + } + tagName = state.input.slice(position, state.position); + if (PATTERN_FLOW_INDICATORS.test(tagName)) { + return throwError(state, "tag suffix cannot contain flow indicator characters"); + } + } + if (tagName && !PATTERN_TAG_URI.test(tagName)) { + return throwError(state, `tag name cannot contain such characters: ${tagName}`); + } + if (isVerbatim) { + state.tag = tagName; + } else if (typeof state.tagMap !== "undefined" && hasOwn2(state.tagMap, tagHandle)) { + state.tag = state.tagMap[tagHandle] + tagName; + } else if (tagHandle === "!") { + state.tag = `!${tagName}`; + } else if (tagHandle === "!!") { + state.tag = `tag:yaml.org,2002:${tagName}`; + } else { + return throwError(state, `undeclared tag handle "${tagHandle}"`); + } + return true; +} +function readAnchorProperty(state) { + let ch = state.input.charCodeAt(state.position); + if (ch !== 0x26) return false; + if (state.anchor !== null) { + return throwError(state, "duplication of an anchor property"); + } + ch = state.input.charCodeAt(++state.position); + const position = state.position; + while(ch !== 0 && !isWsOrEol(ch) && !isFlowIndicator(ch)){ + ch = state.input.charCodeAt(++state.position); + } + if (state.position === position) { + return throwError(state, "name of an anchor node must contain at least one character"); + } + state.anchor = state.input.slice(position, state.position); + return true; +} +function readAlias(state) { + let ch = state.input.charCodeAt(state.position); + if (ch !== 0x2a) return false; + ch = state.input.charCodeAt(++state.position); + const _position = state.position; + while(ch !== 0 && !isWsOrEol(ch) && !isFlowIndicator(ch)){ + ch = state.input.charCodeAt(++state.position); + } + if (state.position === _position) { + return throwError(state, "name of an alias node must contain at least one character"); + } + const alias = state.input.slice(_position, state.position); + if (typeof state.anchorMap !== "undefined" && !hasOwn2(state.anchorMap, alias)) { + return throwError(state, `unidentified alias "${alias}"`); + } + if (typeof state.anchorMap !== "undefined") { + state.result = state.anchorMap[alias]; + } + skipSeparationSpace(state, true, -1); + return true; +} +function composeNode(state, parentIndent, nodeContext, allowToSeek, allowCompact) { + let allowBlockScalars, allowBlockCollections, indentStatus = 1, atNewLine = false, hasContent = false, type, flowIndent, blockIndent; + if (state.listener && state.listener !== null) { + state.listener("open", state); + } + state.tag = null; + state.anchor = null; + state.kind = null; + state.result = null; + const allowBlockStyles = allowBlockScalars = allowBlockCollections = CONTEXT_BLOCK_OUT === nodeContext || CONTEXT_BLOCK_IN === nodeContext; + if (allowToSeek) { + if (skipSeparationSpace(state, true, -1)) { + atNewLine = true; + if (state.lineIndent > parentIndent) { + indentStatus = 1; + } else if (state.lineIndent === parentIndent) { + indentStatus = 0; + } else if (state.lineIndent < parentIndent) { + indentStatus = -1; + } + } + } + if (indentStatus === 1) { + while(readTagProperty(state) || readAnchorProperty(state)){ + if (skipSeparationSpace(state, true, -1)) { + atNewLine = true; + allowBlockCollections = allowBlockStyles; + if (state.lineIndent > parentIndent) { + indentStatus = 1; + } else if (state.lineIndent === parentIndent) { + indentStatus = 0; + } else if (state.lineIndent < parentIndent) { + indentStatus = -1; + } + } else { + allowBlockCollections = false; + } + } + } + if (allowBlockCollections) { + allowBlockCollections = atNewLine || allowCompact; + } + if (indentStatus === 1 || 4 === nodeContext) { + const cond = 1 === nodeContext || 2 === nodeContext; + flowIndent = cond ? parentIndent : parentIndent + 1; + blockIndent = state.position - state.lineStart; + if (indentStatus === 1) { + if (allowBlockCollections && (readBlockSequence(state, blockIndent) || readBlockMapping(state, blockIndent, flowIndent)) || readFlowCollection(state, flowIndent)) { + hasContent = true; + } else { + if (allowBlockScalars && readBlockScalar(state, flowIndent) || readSingleQuotedScalar(state, flowIndent) || readDoubleQuotedScalar(state, flowIndent)) { + hasContent = true; + } else if (readAlias(state)) { + hasContent = true; + if (state.tag !== null || state.anchor !== null) { + return throwError(state, "alias node should not have Any properties"); + } + } else if (readPlainScalar(state, flowIndent, 1 === nodeContext)) { + hasContent = true; + if (state.tag === null) { + state.tag = "?"; + } + } + if (state.anchor !== null && typeof state.anchorMap !== "undefined") { + state.anchorMap[state.anchor] = state.result; + } + } + } else if (indentStatus === 0) { + hasContent = allowBlockCollections && readBlockSequence(state, blockIndent); + } + } + if (state.tag !== null && state.tag !== "!") { + if (state.tag === "?") { + for(let typeIndex = 0, typeQuantity = state.implicitTypes.length; typeIndex < typeQuantity; typeIndex++){ + type = state.implicitTypes[typeIndex]; + if (type.resolve(state.result)) { + state.result = type.construct(state.result); + state.tag = type.tag; + if (state.anchor !== null && typeof state.anchorMap !== "undefined") { + state.anchorMap[state.anchor] = state.result; + } + break; + } + } + } else if (hasOwn2(state.typeMap[state.kind || "fallback"], state.tag)) { + type = state.typeMap[state.kind || "fallback"][state.tag]; + if (state.result !== null && type.kind !== state.kind) { + return throwError(state, `unacceptable node kind for !<${state.tag}> tag; it should be "${type.kind}", not "${state.kind}"`); + } + if (!type.resolve(state.result)) { + return throwError(state, `cannot resolve a node with !<${state.tag}> explicit tag`); + } else { + state.result = type.construct(state.result); + if (state.anchor !== null && typeof state.anchorMap !== "undefined") { + state.anchorMap[state.anchor] = state.result; + } + } + } else { + return throwError(state, `unknown tag !<${state.tag}>`); + } + } + if (state.listener && state.listener !== null) { + state.listener("close", state); + } + return state.tag !== null || state.anchor !== null || hasContent; +} +function readDocument(state) { + const documentStart = state.position; + let position, directiveName, directiveArgs, hasDirectives = false, ch; + state.version = null; + state.checkLineBreaks = state.legacy; + state.tagMap = {}; + state.anchorMap = {}; + while((ch = state.input.charCodeAt(state.position)) !== 0){ + skipSeparationSpace(state, true, -1); + ch = state.input.charCodeAt(state.position); + if (state.lineIndent > 0 || ch !== 0x25) { + break; + } + hasDirectives = true; + ch = state.input.charCodeAt(++state.position); + position = state.position; + while(ch !== 0 && !isWsOrEol(ch)){ + ch = state.input.charCodeAt(++state.position); + } + directiveName = state.input.slice(position, state.position); + directiveArgs = []; + if (directiveName.length < 1) { + return throwError(state, "directive name must not be less than one character in length"); + } + while(ch !== 0){ + while(isWhiteSpace(ch)){ + ch = state.input.charCodeAt(++state.position); + } + if (ch === 0x23) { + do { + ch = state.input.charCodeAt(++state.position); + }while (ch !== 0 && !isEOL(ch)) + break; + } + if (isEOL(ch)) break; + position = state.position; + while(ch !== 0 && !isWsOrEol(ch)){ + ch = state.input.charCodeAt(++state.position); + } + directiveArgs.push(state.input.slice(position, state.position)); + } + if (ch !== 0) readLineBreak(state); + if (hasOwn2(directiveHandlers, directiveName)) { + directiveHandlers[directiveName](state, directiveName, ...directiveArgs); + } else { + throwWarning(state, `unknown document directive "${directiveName}"`); + } + } + skipSeparationSpace(state, true, -1); + if (state.lineIndent === 0 && state.input.charCodeAt(state.position) === 0x2d && state.input.charCodeAt(state.position + 1) === 0x2d && state.input.charCodeAt(state.position + 2) === 0x2d) { + state.position += 3; + skipSeparationSpace(state, true, -1); + } else if (hasDirectives) { + return throwError(state, "directives end mark is expected"); + } + composeNode(state, state.lineIndent - 1, 4, false, true); + skipSeparationSpace(state, true, -1); + if (state.checkLineBreaks && PATTERN_NON_ASCII_LINE_BREAKS.test(state.input.slice(documentStart, state.position))) { + throwWarning(state, "non-ASCII line breaks are interpreted as content"); + } + state.documents.push(state.result); + if (state.position === state.lineStart && testDocumentSeparator(state)) { + if (state.input.charCodeAt(state.position) === 0x2e) { + state.position += 3; + skipSeparationSpace(state, true, -1); + } + return; + } + if (state.position < state.length - 1) { + return throwError(state, "end of the stream or a document separator is expected"); + } else { + return; + } +} +function loadDocuments(input, options) { + input = String(input); + options = options || {}; + if (input.length !== 0) { + if (input.charCodeAt(input.length - 1) !== 0x0a && input.charCodeAt(input.length - 1) !== 0x0d) { + input += "\n"; + } + if (input.charCodeAt(0) === 0xfeff) { + input = input.slice(1); + } + } + const state = new LoaderState(input, options); + state.input += "\0"; + while(state.input.charCodeAt(state.position) === 0x20){ + state.lineIndent += 1; + state.position += 1; + } + while(state.position < state.length - 1){ + readDocument(state); + } + return state.documents; +} +function isCbFunction(fn) { + return typeof fn === "function"; +} +function loadAll(input, iteratorOrOption, options) { + if (!isCbFunction(iteratorOrOption)) { + return loadDocuments(input, iteratorOrOption); + } + const documents = loadDocuments(input, options); + const iterator = iteratorOrOption; + for(let index = 0, length = documents.length; index < length; index++){ + iterator(documents[index]); + } + return void 0; +} +function load(input, options) { + const documents = loadDocuments(input, options); + if (documents.length === 0) { + return; + } + if (documents.length === 1) { + return documents[0]; + } + throw new YAMLError("expected a single document in the stream, but found more"); +} +function parse(content, options) { + return load(content, options); +} +function parseAll(content, iterator, options) { + return loadAll(content, iterator, options); +} +const { hasOwn: hasOwn3 } = Object; +function compileStyleMap(schema, map) { + if (typeof map === "undefined" || map === null) return {}; + let type; + const result = {}; + const keys = Object.keys(map); + let tag, style; + for(let index = 0, length = keys.length; index < length; index += 1){ + tag = keys[index]; + style = String(map[tag]); + if (tag.slice(0, 2) === "!!") { + tag = `tag:yaml.org,2002:${tag.slice(2)}`; + } + type = schema.compiledTypeMap.fallback[tag]; + if (type && typeof type.styleAliases !== "undefined" && hasOwn3(type.styleAliases, style)) { + style = type.styleAliases[style]; + } + result[tag] = style; + } + return result; +} +class DumperState extends State { + indent; + noArrayIndent; + skipInvalid; + flowLevel; + sortKeys; + lineWidth; + noRefs; + noCompatMode; + condenseFlow; + implicitTypes; + explicitTypes; + tag = null; + result = ""; + duplicates = []; + usedDuplicates = []; + styleMap; + dump; + constructor({ schema, indent = 2, noArrayIndent = false, skipInvalid = false, flowLevel = -1, styles = null, sortKeys = false, lineWidth = 80, noRefs = false, noCompatMode = false, condenseFlow = false }){ + super(schema); + this.indent = Math.max(1, indent); + this.noArrayIndent = noArrayIndent; + this.skipInvalid = skipInvalid; + this.flowLevel = flowLevel; + this.styleMap = compileStyleMap(this.schema, styles); + this.sortKeys = sortKeys; + this.lineWidth = lineWidth; + this.noRefs = noRefs; + this.noCompatMode = noCompatMode; + this.condenseFlow = condenseFlow; + this.implicitTypes = this.schema.compiledImplicit; + this.explicitTypes = this.schema.compiledExplicit; + } +} +const _toString2 = Object.prototype.toString; +const { hasOwn: hasOwn4 } = Object; +const ESCAPE_SEQUENCES = {}; +ESCAPE_SEQUENCES[0x00] = "\\0"; +ESCAPE_SEQUENCES[0x07] = "\\a"; +ESCAPE_SEQUENCES[0x08] = "\\b"; +ESCAPE_SEQUENCES[0x09] = "\\t"; +ESCAPE_SEQUENCES[0x0a] = "\\n"; +ESCAPE_SEQUENCES[0x0b] = "\\v"; +ESCAPE_SEQUENCES[0x0c] = "\\f"; +ESCAPE_SEQUENCES[0x0d] = "\\r"; +ESCAPE_SEQUENCES[0x1b] = "\\e"; +ESCAPE_SEQUENCES[0x22] = '\\"'; +ESCAPE_SEQUENCES[0x5c] = "\\\\"; +ESCAPE_SEQUENCES[0x85] = "\\N"; +ESCAPE_SEQUENCES[0xa0] = "\\_"; +ESCAPE_SEQUENCES[0x2028] = "\\L"; +ESCAPE_SEQUENCES[0x2029] = "\\P"; +const DEPRECATED_BOOLEANS_SYNTAX = [ + "y", + "Y", + "yes", + "Yes", + "YES", + "on", + "On", + "ON", + "n", + "N", + "no", + "No", + "NO", + "off", + "Off", + "OFF" +]; +function encodeHex(character) { + const string = character.toString(16).toUpperCase(); + let handle; + let length; + if (character <= 0xff) { + handle = "x"; + length = 2; + } else if (character <= 0xffff) { + handle = "u"; + length = 4; + } else if (character <= 0xffffffff) { + handle = "U"; + length = 8; + } else { + throw new YAMLError("code point within a string may not be greater than 0xFFFFFFFF"); + } + return `\\${handle}${repeat("0", length - string.length)}${string}`; +} +function indentString(string, spaces) { + const ind = repeat(" ", spaces), length = string.length; + let position = 0, next = -1, result = "", line; + while(position < length){ + next = string.indexOf("\n", position); + if (next === -1) { + line = string.slice(position); + position = length; + } else { + line = string.slice(position, next + 1); + position = next + 1; + } + if (line.length && line !== "\n") result += ind; + result += line; + } + return result; +} +function generateNextLine(state, level) { + return `\n${repeat(" ", state.indent * level)}`; +} +function testImplicitResolving(state, str) { + let type; + for(let index = 0, length = state.implicitTypes.length; index < length; index += 1){ + type = state.implicitTypes[index]; + if (type.resolve(str)) { + return true; + } + } + return false; +} +function isWhitespace(c) { + return c === 0x20 || c === 0x09; +} +function isPrintable(c) { + return 0x00020 <= c && c <= 0x00007e || 0x000a1 <= c && c <= 0x00d7ff && c !== 0x2028 && c !== 0x2029 || 0x0e000 <= c && c <= 0x00fffd && c !== 0xfeff || 0x10000 <= c && c <= 0x10ffff; +} +function isPlainSafe(c) { + return isPrintable(c) && c !== 0xfeff && c !== 0x2c && c !== 0x5b && c !== 0x5d && c !== 0x7b && c !== 0x7d && c !== 0x3a && c !== 0x23; +} +function isPlainSafeFirst(c) { + return isPrintable(c) && c !== 0xfeff && !isWhitespace(c) && c !== 0x2d && c !== 0x3f && c !== 0x3a && c !== 0x2c && c !== 0x5b && c !== 0x5d && c !== 0x7b && c !== 0x7d && c !== 0x23 && c !== 0x26 && c !== 0x2a && c !== 0x21 && c !== 0x7c && c !== 0x3e && c !== 0x27 && c !== 0x22 && c !== 0x25 && c !== 0x40 && c !== 0x60; +} +function needIndentIndicator(string) { + const leadingSpaceRe = /^\n* /; + return leadingSpaceRe.test(string); +} +const STYLE_PLAIN = 1, STYLE_SINGLE = 2, STYLE_LITERAL = 3, STYLE_FOLDED = 4, STYLE_DOUBLE = 5; +function chooseScalarStyle(string, singleLineOnly, indentPerLevel, lineWidth, testAmbiguousType) { + const shouldTrackWidth = lineWidth !== -1; + let hasLineBreak = false, hasFoldableLine = false, previousLineBreak = -1, plain = isPlainSafeFirst(string.charCodeAt(0)) && !isWhitespace(string.charCodeAt(string.length - 1)); + let __char, i; + if (singleLineOnly) { + for(i = 0; i < string.length; i++){ + __char = string.charCodeAt(i); + if (!isPrintable(__char)) { + return 5; + } + plain = plain && isPlainSafe(__char); + } + } else { + for(i = 0; i < string.length; i++){ + __char = string.charCodeAt(i); + if (__char === 0x0a) { + hasLineBreak = true; + if (shouldTrackWidth) { + hasFoldableLine = hasFoldableLine || i - previousLineBreak - 1 > lineWidth && string[previousLineBreak + 1] !== " "; + previousLineBreak = i; + } + } else if (!isPrintable(__char)) { + return 5; + } + plain = plain && isPlainSafe(__char); + } + hasFoldableLine = hasFoldableLine || shouldTrackWidth && i - previousLineBreak - 1 > lineWidth && string[previousLineBreak + 1] !== " "; + } + if (!hasLineBreak && !hasFoldableLine) { + return plain && !testAmbiguousType(string) ? 1 : 2; + } + if (indentPerLevel > 9 && needIndentIndicator(string)) { + return 5; + } + return hasFoldableLine ? 4 : 3; +} +function foldLine(line, width) { + if (line === "" || line[0] === " ") return line; + const breakRe = / [^ ]/g; + let match; + let start = 0, end, curr = 0, next = 0; + let result = ""; + while(match = breakRe.exec(line)){ + next = match.index; + if (next - start > width) { + end = curr > start ? curr : next; + result += `\n${line.slice(start, end)}`; + start = end + 1; + } + curr = next; + } + result += "\n"; + if (line.length - start > width && curr > start) { + result += `${line.slice(start, curr)}\n${line.slice(curr + 1)}`; + } else { + result += line.slice(start); + } + return result.slice(1); +} +function dropEndingNewline(string) { + return string[string.length - 1] === "\n" ? string.slice(0, -1) : string; +} +function foldString(string, width) { + const lineRe = /(\n+)([^\n]*)/g; + let result = (()=>{ + let nextLF = string.indexOf("\n"); + nextLF = nextLF !== -1 ? nextLF : string.length; + lineRe.lastIndex = nextLF; + return foldLine(string.slice(0, nextLF), width); + })(); + let prevMoreIndented = string[0] === "\n" || string[0] === " "; + let moreIndented; + let match; + while(match = lineRe.exec(string)){ + const prefix = match[1], line = match[2]; + moreIndented = line[0] === " "; + result += prefix + (!prevMoreIndented && !moreIndented && line !== "" ? "\n" : "") + foldLine(line, width); + prevMoreIndented = moreIndented; + } + return result; +} +function escapeString(string) { + let result = ""; + let __char, nextChar; + let escapeSeq; + for(let i = 0; i < string.length; i++){ + __char = string.charCodeAt(i); + if (__char >= 0xd800 && __char <= 0xdbff) { + nextChar = string.charCodeAt(i + 1); + if (nextChar >= 0xdc00 && nextChar <= 0xdfff) { + result += encodeHex((__char - 0xd800) * 0x400 + nextChar - 0xdc00 + 0x10000); + i++; + continue; + } + } + escapeSeq = ESCAPE_SEQUENCES[__char]; + result += !escapeSeq && isPrintable(__char) ? string[i] : escapeSeq || encodeHex(__char); + } + return result; +} +function blockHeader(string, indentPerLevel) { + const indentIndicator = needIndentIndicator(string) ? String(indentPerLevel) : ""; + const clip = string[string.length - 1] === "\n"; + const keep = clip && (string[string.length - 2] === "\n" || string === "\n"); + const chomp = keep ? "+" : clip ? "" : "-"; + return `${indentIndicator}${chomp}\n`; +} +function writeScalar(state, string, level, iskey) { + state.dump = (()=>{ + if (string.length === 0) { + return "''"; + } + if (!state.noCompatMode && DEPRECATED_BOOLEANS_SYNTAX.indexOf(string) !== -1) { + return `'${string}'`; + } + const indent = state.indent * Math.max(1, level); + const lineWidth = state.lineWidth === -1 ? -1 : Math.max(Math.min(state.lineWidth, 40), state.lineWidth - indent); + const singleLineOnly = iskey || state.flowLevel > -1 && level >= state.flowLevel; + function testAmbiguity(str) { + return testImplicitResolving(state, str); + } + switch(chooseScalarStyle(string, singleLineOnly, state.indent, lineWidth, testAmbiguity)){ + case STYLE_PLAIN: + return string; + case STYLE_SINGLE: + return `'${string.replace(/'/g, "''")}'`; + case STYLE_LITERAL: + return `|${blockHeader(string, state.indent)}${dropEndingNewline(indentString(string, indent))}`; + case STYLE_FOLDED: + return `>${blockHeader(string, state.indent)}${dropEndingNewline(indentString(foldString(string, lineWidth), indent))}`; + case STYLE_DOUBLE: + return `"${escapeString(string)}"`; + default: + throw new YAMLError("impossible error: invalid scalar style"); + } + })(); +} +function writeFlowSequence(state, level, object) { + let _result = ""; + const _tag = state.tag; + for(let index = 0, length = object.length; index < length; index += 1){ + if (writeNode(state, level, object[index], false, false)) { + if (index !== 0) _result += `,${!state.condenseFlow ? " " : ""}`; + _result += state.dump; + } + } + state.tag = _tag; + state.dump = `[${_result}]`; +} +function writeBlockSequence(state, level, object, compact = false) { + let _result = ""; + const _tag = state.tag; + for(let index = 0, length = object.length; index < length; index += 1){ + if (writeNode(state, level + 1, object[index], true, true)) { + if (!compact || index !== 0) { + _result += generateNextLine(state, level); + } + if (state.dump && 0x0a === state.dump.charCodeAt(0)) { + _result += "-"; + } else { + _result += "- "; + } + _result += state.dump; + } + } + state.tag = _tag; + state.dump = _result || "[]"; +} +function writeFlowMapping(state, level, object) { + let _result = ""; + const _tag = state.tag, objectKeyList = Object.keys(object); + let pairBuffer, objectKey, objectValue; + for(let index = 0, length = objectKeyList.length; index < length; index += 1){ + pairBuffer = state.condenseFlow ? '"' : ""; + if (index !== 0) pairBuffer += ", "; + objectKey = objectKeyList[index]; + objectValue = object[objectKey]; + if (!writeNode(state, level, objectKey, false, false)) { + continue; + } + if (state.dump.length > 1024) pairBuffer += "? "; + pairBuffer += `${state.dump}${state.condenseFlow ? '"' : ""}:${state.condenseFlow ? "" : " "}`; + if (!writeNode(state, level, objectValue, false, false)) { + continue; + } + pairBuffer += state.dump; + _result += pairBuffer; + } + state.tag = _tag; + state.dump = `{${_result}}`; +} +function writeBlockMapping(state, level, object, compact = false) { + const _tag = state.tag, objectKeyList = Object.keys(object); + let _result = ""; + if (state.sortKeys === true) { + objectKeyList.sort(); + } else if (typeof state.sortKeys === "function") { + objectKeyList.sort(state.sortKeys); + } else if (state.sortKeys) { + throw new YAMLError("sortKeys must be a boolean or a function"); + } + let pairBuffer = "", objectKey, objectValue, explicitPair; + for(let index = 0, length = objectKeyList.length; index < length; index += 1){ + pairBuffer = ""; + if (!compact || index !== 0) { + pairBuffer += generateNextLine(state, level); + } + objectKey = objectKeyList[index]; + objectValue = object[objectKey]; + if (!writeNode(state, level + 1, objectKey, true, true, true)) { + continue; + } + explicitPair = state.tag !== null && state.tag !== "?" || state.dump && state.dump.length > 1024; + if (explicitPair) { + if (state.dump && 0x0a === state.dump.charCodeAt(0)) { + pairBuffer += "?"; + } else { + pairBuffer += "? "; + } + } + pairBuffer += state.dump; + if (explicitPair) { + pairBuffer += generateNextLine(state, level); + } + if (!writeNode(state, level + 1, objectValue, true, explicitPair)) { + continue; + } + if (state.dump && 0x0a === state.dump.charCodeAt(0)) { + pairBuffer += ":"; + } else { + pairBuffer += ": "; + } + pairBuffer += state.dump; + _result += pairBuffer; + } + state.tag = _tag; + state.dump = _result || "{}"; +} +function detectType(state, object, explicit = false) { + const typeList = explicit ? state.explicitTypes : state.implicitTypes; + let type; + let style; + let _result; + for(let index = 0, length = typeList.length; index < length; index += 1){ + type = typeList[index]; + if ((type.instanceOf || type.predicate) && (!type.instanceOf || typeof object === "object" && object instanceof type.instanceOf) && (!type.predicate || type.predicate(object))) { + state.tag = explicit ? type.tag : "?"; + if (type.represent) { + style = state.styleMap[type.tag] || type.defaultStyle; + if (_toString2.call(type.represent) === "[object Function]") { + _result = type.represent(object, style); + } else if (hasOwn4(type.represent, style)) { + _result = type.represent[style](object, style); + } else { + throw new YAMLError(`!<${type.tag}> tag resolver accepts not "${style}" style`); + } + state.dump = _result; + } + return true; + } + } + return false; +} +function writeNode(state, level, object, block, compact, iskey = false) { + state.tag = null; + state.dump = object; + if (!detectType(state, object, false)) { + detectType(state, object, true); + } + const type = _toString2.call(state.dump); + if (block) { + block = state.flowLevel < 0 || state.flowLevel > level; + } + const objectOrArray = type === "[object Object]" || type === "[object Array]"; + let duplicateIndex = -1; + let duplicate = false; + if (objectOrArray) { + duplicateIndex = state.duplicates.indexOf(object); + duplicate = duplicateIndex !== -1; + } + if (state.tag !== null && state.tag !== "?" || duplicate || state.indent !== 2 && level > 0) { + compact = false; + } + if (duplicate && state.usedDuplicates[duplicateIndex]) { + state.dump = `*ref_${duplicateIndex}`; + } else { + if (objectOrArray && duplicate && !state.usedDuplicates[duplicateIndex]) { + state.usedDuplicates[duplicateIndex] = true; + } + if (type === "[object Object]") { + if (block && Object.keys(state.dump).length !== 0) { + writeBlockMapping(state, level, state.dump, compact); + if (duplicate) { + state.dump = `&ref_${duplicateIndex}${state.dump}`; + } + } else { + writeFlowMapping(state, level, state.dump); + if (duplicate) { + state.dump = `&ref_${duplicateIndex} ${state.dump}`; + } + } + } else if (type === "[object Array]") { + const arrayLevel = state.noArrayIndent && level > 0 ? level - 1 : level; + if (block && state.dump.length !== 0) { + writeBlockSequence(state, arrayLevel, state.dump, compact); + if (duplicate) { + state.dump = `&ref_${duplicateIndex}${state.dump}`; + } + } else { + writeFlowSequence(state, arrayLevel, state.dump); + if (duplicate) { + state.dump = `&ref_${duplicateIndex} ${state.dump}`; + } + } + } else if (type === "[object String]") { + if (state.tag !== "?") { + writeScalar(state, state.dump, level, iskey); + } + } else { + if (state.skipInvalid) return false; + throw new YAMLError(`unacceptable kind of an object to dump ${type}`); + } + if (state.tag !== null && state.tag !== "?") { + state.dump = `!<${state.tag}> ${state.dump}`; + } + } + return true; +} +function inspectNode(object, objects, duplicatesIndexes) { + if (object !== null && typeof object === "object") { + const index = objects.indexOf(object); + if (index !== -1) { + if (duplicatesIndexes.indexOf(index) === -1) { + duplicatesIndexes.push(index); + } + } else { + objects.push(object); + if (Array.isArray(object)) { + for(let idx = 0, length = object.length; idx < length; idx += 1){ + inspectNode(object[idx], objects, duplicatesIndexes); + } + } else { + const objectKeyList = Object.keys(object); + for(let idx = 0, length = objectKeyList.length; idx < length; idx += 1){ + inspectNode(object[objectKeyList[idx]], objects, duplicatesIndexes); + } + } + } + } +} +function getDuplicateReferences(object, state) { + const objects = [], duplicatesIndexes = []; + inspectNode(object, objects, duplicatesIndexes); + const length = duplicatesIndexes.length; + for(let index = 0; index < length; index += 1){ + state.duplicates.push(objects[duplicatesIndexes[index]]); + } + state.usedDuplicates = Array.from({ + length + }); +} +function dump(input, options) { + options = options || {}; + const state = new DumperState(options); + if (!state.noRefs) getDuplicateReferences(input, state); + if (writeNode(state, 0, input, true, true)) return `${state.dump}\n`; + return ""; +} +function stringify(obj, options) { + return dump(obj, options); +} +const mod1 = { + parse: parse, + parseAll: parseAll, + stringify: stringify, + Type: Type, + CORE_SCHEMA: core, + DEFAULT_SCHEMA: def, + EXTENDED_SCHEMA: extended, + FAILSAFE_SCHEMA: failsafe, + JSON_SCHEMA: json +}; +function incrementLastNumber(list) { + const newList = [ + ...list + ]; + newList[newList.length - 1]++; + return newList; +} +class EmVer { + values; + static from(range) { + if (range instanceof EmVer) { + return range; + } + return EmVer.parse(range); + } + static parse(range) { + const values = range.split(".").map((x)=>parseInt(x)); + for (const value of values){ + if (isNaN(value)) { + throw new Error(`Couldn't parse range: ${range}`); + } + } + return new EmVer(values); + } + constructor(values){ + this.values = values; + } + withLastIncremented() { + return new EmVer(incrementLastNumber(this.values)); + } + greaterThan(other) { + for(const i in this.values){ + if (other.values[i] == null) { + return true; + } + if (this.values[i] > other.values[i]) { + return true; + } + if (this.values[i] < other.values[i]) { + return false; + } + } + return false; + } + equals(other) { + if (other.values.length !== this.values.length) { + return false; + } + for(const i in this.values){ + if (this.values[i] !== other.values[i]) { + return false; + } + } + return true; + } + greaterThanOrEqual(other) { + return this.greaterThan(other) || this.equals(other); + } + lessThanOrEqual(other) { + return !this.greaterThan(other); + } + lessThan(other) { + return !this.greaterThanOrEqual(other); + } + compare(other) { + if (this.equals(other)) { + return "equal"; + } else if (this.greaterThan(other)) { + return "greater"; + } else { + return "less"; + } + } + compareForSort(other) { + return mod.matches(this.compare(other)).when("equal", ()=>0).when("greater", ()=>1).when("less", ()=>-1).unwrap(); + } +} +function migrationFn(fn) { + return fn; +} +function fromMapping(migrations, currentVersion) { + const directionShape = mod.literals("from", "to"); + return async (effects, version, direction)=>{ + if (!directionShape.test(direction)) { + return { + error: 'Must specify arg "from" or "to".' + }; + } + let configured = true; + const current = EmVer.parse(currentVersion); + const other = EmVer.parse(version); + const filteredMigrations = Object.entries(migrations).map(([version, migration])=>({ + version: EmVer.parse(version), + migration + })).filter(({ version })=>version.greaterThan(other) && version.lessThanOrEqual(current)); + const migrationsToRun = mod.matches(direction).when("from", ()=>filteredMigrations.sort((a, b)=>a.version.compareForSort(b.version)).map(({ migration })=>migration.up)).when("to", ()=>filteredMigrations.sort((a, b)=>b.version.compareForSort(a.version)).map(({ migration })=>migration.down)).unwrap(); + for (const migration of migrationsToRun){ + configured = (await migration(effects)).configured && configured; + } + return { + result: { + configured + } + }; + }; +} +function unwrapResultType(res) { + if ("error-code" in res) { + throw new Error(res["error-code"][1]); + } else if ("error" in res) { + throw new Error(res["error"]); + } else { + return res.result; + } +} +const exists = (effects, props)=>effects.metadata(props).then((_)=>true, (_)=>false); +const asResult = (result)=>({ + result: result + }); +const noPropertiesFound = { + result: { + version: 2, + data: { + "Not Ready": { + type: "string", + value: "Could not find properties. The service might still be starting", + qr: false, + copyable: false, + masked: false, + description: "Fallback Message When Properties could not be found" + } + } + } +}; +const properties = async (effects)=>{ + if (await exists(effects, { + path: "start9/stats.yaml", + volumeId: "main" + }) === false) { + return noPropertiesFound; + } + return await effects.readFile({ + path: "start9/stats.yaml", + volumeId: "main" + }).then(mod1.parse).then(asResult); +}; +const setConfig = async (effects, newConfig, dependsOn = {})=>{ + await effects.createDir({ + path: "start9", + volumeId: "main" + }); + await effects.writeFile({ + path: "start9/config.yaml", + toWrite: mod1.stringify(newConfig), + volumeId: "main" + }); + const result = { + signal: "SIGTERM", + "depends-on": dependsOn + }; + return { + result + }; +}; +const { any: any1, string: string1, dictionary: dictionary1 } = mod; +const matchConfig = dictionary1([ + string1, + any1 +]); +const getConfig = (spec)=>async (effects)=>{ + const config = await effects.readFile({ + path: "start9/config.yaml", + volumeId: "main" + }).then((x)=>mod1.parse(x)).then((x)=>matchConfig.unsafeCast(x)).catch((e)=>{ + effects.info(`Got error ${e} while trying to read the config`); + return undefined; + }); + return { + result: { + config, + spec + } + }; + }; +function updateConfig(fn, configured, noRepeat, noFail = false) { + return migrationFn(async (effects)=>{ + await noRepeatGuard(effects, noRepeat, async ()=>{ + let config = unwrapResultType(await getConfig({})(effects)).config; + if (config) { + try { + config = await fn(config, effects); + } catch (e) { + if (!noFail) { + throw e; + } else { + configured = false; + } + } + unwrapResultType(await setConfig(effects, config)); + } + }); + return { + configured + }; + }); +} +async function noRepeatGuard(effects, noRepeat, fn) { + if (!noRepeat) { + return fn(); + } + if (!await exists(effects, { + path: "start9/migrations", + volumeId: "main" + })) { + await effects.createDir({ + path: "start9/migrations", + volumeId: "main" + }); + } + const migrationPath = { + path: `start9/migrations/${noRepeat.version}.complete`, + volumeId: "main" + }; + if (noRepeat.type === "up") { + if (!await exists(effects, migrationPath)) { + await fn(); + await effects.writeFile({ + ...migrationPath, + toWrite: "" + }); + } + } else if (noRepeat.type === "down") { + if (await exists(effects, migrationPath)) { + await fn(); + await effects.removeFile(migrationPath); + } + } +} +async function initNoRepeat(effects, migrations, startingVersion) { + if (!await exists(effects, { + path: "start9/migrations", + volumeId: "main" + })) { + const starting = EmVer.parse(startingVersion); + await effects.createDir({ + path: "start9/migrations", + volumeId: "main" + }); + for(const version in migrations){ + const migrationVersion = EmVer.parse(version); + if (migrationVersion.lessThanOrEqual(starting)) { + await effects.writeFile({ + path: `start9/migrations/${version}.complete`, + volumeId: "main", + toWrite: "" + }); + } + } + } +} +function fromMapping1(migrations, currentVersion) { + const inner = fromMapping(migrations, currentVersion); + return async (effects, version, direction)=>{ + await initNoRepeat(effects, migrations, direction === "from" ? version : currentVersion); + return inner(effects, version, direction); + }; +} +const mod2 = { + updateConfig: updateConfig, + noRepeatGuard: noRepeatGuard, + initNoRepeat: initNoRepeat, + fromMapping: fromMapping1 +}; +const mod3 = { + properties: properties, + setConfig: setConfig, + getConfig: getConfig, + migrations: mod2 +}; +const setConfig1 = async (effects, input)=>{ + const newConfig = input; + const depsLnd = newConfig?.implementation === "LndRestWallet" ? { + lnd: [] + } : {}; + const depsCln = newConfig?.implementation === "CLightningWallet" ? { + "c-lightning": [] + } : {}; + return await mod3.setConfig(effects, input, { + ...depsLnd, + ...depsCln + }); +}; +const properties1 = mod3.properties; +const getConfig1 = mod3.getConfig({ + "tor-address": { + "name": "Tor Address", + "description": "The Tor address of the network interface", + "type": "pointer", + "subtype": "package", + "package-id": "lnbits", + "target": "tor-address", + "interface": "main" + }, + "lan-address": { + "name": "LAN Address", + "description": "The LAN address of the network interface", + "type": "pointer", + "subtype": "package", + "package-id": "lnbits", + "target": "lan-address", + "interface": "main" + }, + "implementation": { + "type": "enum", + "name": "Lightning Implementation", + "description": "The underlying Lightning implementation, currently LND or Core Lightning (CLN)", + "warning": "If you change the LN implementation after using LNBits this will delete all LNBits accounts and wallets related to the previously configured LN implementation! All LN funds will still be available on the underlying LN implementation.", + "values": [ + "LndRestWallet", + "CLightningWallet" + ], + 'value-names': { + "LndRestWallet": "LND", + "CLightningWallet": "Core Lightning" + }, + "default": "LndRestWallet" + } +}); +const migration = mod3.migrations.fromMapping({ + "0.9.7.2": { + up: mod3.migrations.updateConfig((config)=>{ + return { + implementation: config?.wallet.type || "LndRestWallet" + }; + }, true, { + version: "0.9.7.2", + type: "up" + }), + down: mod3.migrations.updateConfig((config)=>{ + return { + wallet: { + type: config.implementation || "LndRestWallet" + } + }; + }, true, { + version: "0.9.7.2", + type: "down" + }) + }, + "1.0.0": { + up: mod3.migrations.updateConfig((config)=>{ + return config; + }, true, { + version: "1.0.0", + type: "up" + }), + down: ()=>{ + throw new Error("Cannot downgrade"); + } + } +}, "1.1.0"); +export { setConfig1 as setConfig }; +export { properties1 as properties }; +export { getConfig1 as getConfig }; +export { migration as migration }; diff --git a/scripts/embassy.ts b/Old/scripts/embassy.ts similarity index 100% rename from scripts/embassy.ts rename to Old/scripts/embassy.ts diff --git a/scripts/services/getConfig.ts b/Old/scripts/services/getConfig.ts similarity index 100% rename from scripts/services/getConfig.ts rename to Old/scripts/services/getConfig.ts diff --git a/scripts/services/healthChecks.ts b/Old/scripts/services/healthChecks.ts similarity index 100% rename from scripts/services/healthChecks.ts rename to Old/scripts/services/healthChecks.ts diff --git a/scripts/services/migrations.ts b/Old/scripts/services/migrations.ts similarity index 100% rename from scripts/services/migrations.ts rename to Old/scripts/services/migrations.ts diff --git a/scripts/services/properties.ts b/Old/scripts/services/properties.ts similarity index 100% rename from scripts/services/properties.ts rename to Old/scripts/services/properties.ts diff --git a/scripts/services/setConfig.ts b/Old/scripts/services/setConfig.ts similarity index 100% rename from scripts/services/setConfig.ts rename to Old/scripts/services/setConfig.ts diff --git a/.github/workflows/buildService.yml b/Old/workflows/buildService.yml similarity index 100% rename from .github/workflows/buildService.yml rename to Old/workflows/buildService.yml diff --git a/.github/workflows/releaseService.yml b/Old/workflows/releaseService.yml similarity index 100% rename from .github/workflows/releaseService.yml rename to Old/workflows/releaseService.yml diff --git a/README.md b/README.md index 8f65e54..23ad797 100644 --- a/README.md +++ b/README.md @@ -4,64 +4,4 @@ # LNbits for StartOS -lnbits-startos contains the [lnbits](https://github.com/lnbits/lnbits) software packaged to run on StartOS. You can run lnbits on StartOS by installing a .s9pk file, or you can build your own .s9pk file by following the instuctions below. - -## Dependencies - -- [deno](https://deno.land/) -- [docker](https://docs.docker.com/get-docker) -- [docker-buildx](https://docs.docker.com/buildx/working-with-buildx/) -- [make](https://www.gnu.org/software/make/) -- [start-sdk](https://github.com/Start9Labs/start-os/blob/v0.3.5.1/core/install-sdk.sh) -- [yq](https://mikefarah.gitbook.io/yq) - -## Build environment - -Before building the lnbits package, your build environment must be setup for building StartOS services. Instructions for setting up the proper build environment can be found in the [Developer Docs](https://docs.start9.com/latest/developer-docs/packaging). - -## Cloning - -Clone the project locally. - -``` -git clone https://github.com/Start9Labs/lnbits-startos.git -cd lnbits-startos -``` - -## Building - -To build the **lnbits** service as a universal package, run the following command: - -``` -make -``` - -Alternatively the package can be built for individual architectures by specifying the architecture as follows: - -``` -make x86 -``` - -or - -``` -make arm -``` - -## Installing (on StartOS) - -Run the following commands to determine successful install: -> :information_source: Change server-name.local to your Start9 server address - -``` -start-cli auth login -#Enter your StartOS password -start-cli --host https://server-name.local package install lnbits.s9pk -``` -**Tip:** You can also install the lnbits.s9pk using **Sideload Service** under the **StartOS > SETTINGS** section. - -## Verify Install - -Go to your StartOS Services page, select **lnbits**, configure and start the service. - -**Done!** +This repo packages [LNbits](https://github.com/lnbits/lnbits) for StartOS. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..7dcba4c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,278 @@ +{ + "name": "hello-world-startos", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "hello-world-startos", + "dependencies": { + "@start9labs/start-sdk": "^0.4.0-beta.36", + "js-yaml": "^4.1.0" + }, + "devDependencies": { + "@types/js-yaml": "^4.0.9", + "@types/node": "^22.16.2", + "@vercel/ncc": "^0.38.3", + "prettier": "^3.6.2", + "typescript": "^5.8.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/@start9labs/start-sdk": { + "version": "0.4.0-beta.36", + "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.36.tgz", + "integrity": "sha512-26C1NGJBy/yubp88SriuX2wfVDAM6/1rDx47wdypR2KEqGzhOrMRQC5hJDAMU8BeRAiL6ED8/Ca9CP+5+8BBBw==", + "license": "MIT", + "dependencies": { + "@iarna/toml": "^3.0.0", + "@noble/curves": "^1.8.2", + "@noble/hashes": "^1.7.2", + "@types/ini": "^4.1.1", + "deep-equality-data-structures": "^2.0.0", + "ini": "^5.0.0", + "isomorphic-fetch": "^3.0.0", + "mime": "^4.0.7", + "ts-matches": "^6.3.2", + "yaml": "^2.7.1" + } + }, + "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/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.17.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.17.2.tgz", + "integrity": "sha512-gL6z5N9Jm9mhY+U2KXZpteb+09zyffliRkZyZOHODGATyC5B1Jt/7TzuuiLkFsSUMLbS1OLmlj/E+/3KF4Q/4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@vercel/ncc": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.38.3.tgz", + "integrity": "sha512-rnK6hJBS6mwc+Bkab+PGPs9OiS0i/3kdTO+CkI8V0/VrW3vmz7O2Pxjw/owOlmo6PKEIxRSeZKv/kuL9itnpYA==", + "dev": true, + "license": "MIT", + "bin": { + "ncc": "dist/ncc/cli.js" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "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/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/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/mime": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.7.tgz", + "integrity": "sha512-2OfDPL+e03E0LrXaGYOtTFIYhiuzep94NSsuhrNULq+stylcJedcHdzHtz0atMUuGwJfFYs0YL5xeC/Ca2x0eQ==", + "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/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "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/ts-matches": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/ts-matches/-/ts-matches-6.5.0.tgz", + "integrity": "sha512-MhuobYhHYn6MlOTPAF/qk3tsRRioPac5ofYn68tc3rAJaGjsw1MsX1MOSep52DkvNJPgNV0F73zfgcQfYTVeyQ==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "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/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..7dd0d36 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "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": "^0.4.0-beta.36", + "js-yaml": "^4.1.0" + }, + "devDependencies": { + "@types/js-yaml": "^4.0.9", + "@types/node": "^22.16.2", + "@vercel/ncc": "^0.38.3", + "prettier": "^3.6.2", + "typescript": "^5.8.3" + }, + "prettier": { + "trailingComma": "all", + "tabWidth": 2, + "semi": false, + "singleQuote": true + } +} diff --git a/startos/actions/index.ts b/startos/actions/index.ts new file mode 100644 index 0000000..07f5f90 --- /dev/null +++ b/startos/actions/index.ts @@ -0,0 +1,7 @@ +import { sdk } from '../sdk' +import { setLnImplementation } from './lightningImplementation' +import { resetPassword } from './resetPassword' + +export const actions = sdk.Actions.of() + .addAction(setLnImplementation) + .addAction(resetPassword) diff --git a/startos/actions/lightningImplementation.ts b/startos/actions/lightningImplementation.ts new file mode 100644 index 0000000..60eb8e7 --- /dev/null +++ b/startos/actions/lightningImplementation.ts @@ -0,0 +1,74 @@ +import { access, rm } from 'fs/promises' +import { envFile } from '../fileModels/env' +import { sdk } from '../sdk' + +const { InputSpec, Value } = sdk + +export const inputSpec = InputSpec.of({ + implementation: Value.select({ + name: 'Lightning Implementation', + description: + 'The underlying Lightning implementation, currently LND or Core Lightning (CLN)', + values: { + LndRestWallet: 'LND', + CoreLightningWallet: 'Core Lightning', + }, + default: 'LndRestWallet', + }), +}) + +export const setLnImplementation = sdk.Action.withInput( + // id + 'set-lightning-implementation', + + // metadata + async ({ effects }) => ({ + name: 'Lightning Implementation', + description: 'Select the Lightning Implementation for LNbits to utilize', + warning: + 'If the LN implementation is changed after using LNBits this will delete all LNBits accounts and wallets related to the previously configured LN implementation! All LN funds will still be available on the underlying LN implementation.', + allowedStatuses: 'any', + group: null, + visibility: 'enabled', + }), + + // form input specification + inputSpec, + + // optionally pre-fill the input form + async ({ effects }) => { + const configuredLnImplementation = await envFile + .read((e) => e.LNBITS_BACKEND_WALLET_CLASS) + .const(effects) + + if (!configuredLnImplementation) return + + return { + implementation: configuredLnImplementation, + } + }, + + // the execution function + async ({ effects, input }) => { + const configuredLnImplementation = await envFile + .read((e) => e.LNBITS_BACKEND_WALLET_CLASS) + .const(effects) + + try { + await access('/media/startos/volumes/main/database.sqlite3') + if (configuredLnImplementation !== input.implementation) { + console.log( + 'existing LN implementation does not match input. Resetting DB...', + ) + await rm('/media/startos/volumes/main/database.sqlite3') + } + } catch (error) { + console.log('DB has not been initialized') + } + + await envFile.merge(effects, { + LNBITS_BACKEND_WALLET_CLASS: input.implementation, + LNBITS_ALLOWED_FUNDING_SOURCES: input.implementation, + }) + }, +) diff --git a/startos/actions/resetPassword.ts b/startos/actions/resetPassword.ts new file mode 100644 index 0000000..1e41af5 --- /dev/null +++ b/startos/actions/resetPassword.ts @@ -0,0 +1,79 @@ +import { access } from 'fs/promises' +import { sdk } from '../sdk' +import { db, mainMounts, randomPassword } from '../utils' +import { utils } from '@start9labs/start-sdk' + +export const resetPassword = sdk.Action.withoutInput( + // id + 'reset-password', + + // metadata + async ({ effects }) => ({ + name: 'Reset Password', + description: + 'Reset Password for the super_user in the event of a lost or forgotten password', + warning: null, + allowedStatuses: 'only-running', + group: null, + visibility: 'enabled', + }), + + // the execution function + async ({ effects }) => { + const newPassword = utils.getDefaultString(randomPassword) + console.log('newPassword', newPassword) + + await sdk.SubContainer.withTemp( + effects, + { imageId: 'lnbits' }, + mainMounts, + 'reset-pass', + async (subc) => { + const superuserAccountId = await subc.execFail([ + 'sqlite3', + db, + "select value from system_settings where id = 'super_user';", + ]) + + console.log( + 'superuserAccountId', + superuserAccountId.stdout.toString().trimEnd(), + ) + + const newPasswordHash = await subc.execFail([ + 'python3', + '-c', + `import bcrypt; print(bcrypt.hashpw('${newPassword}'.encode('utf-8'), bcrypt.gensalt()).decode('utf-8'))`, + ]) + + console.log( + 'newPasswordHash', + newPasswordHash.stdout.toString().trimEnd(), + ) + + const currentTime = await subc.execFail(['date', '+%s']) + console.log('currentTime', currentTime.stdout.toString().trimEnd()) + + const res = await subc.execFail([ + 'sqlite3', + db, + `update accounts set password_hash = \"${newPasswordHash.stdout.toString().trimEnd()}\", updated_at = ${currentTime.stdout.toString().trimEnd()} where id = ${superuserAccountId.stdout.toString().trimEnd()};`, + ]) + + console.log('res', res) + }, + ) + return { + version: '1', + title: 'Success', + message: 'The Super User new password is below', + result: { + type: 'single', + value: newPassword, + masked: true, + copyable: true, + qr: false, + }, + } + }, +) diff --git a/startos/backups.ts b/startos/backups.ts new file mode 100644 index 0000000..0a90b1e --- /dev/null +++ b/startos/backups.ts @@ -0,0 +1,5 @@ +import { sdk } from './sdk' + +export const { createBackup, restoreInit } = sdk.setupBackups( + async ({ effects }) => sdk.Backups.ofVolumes('main'), +) diff --git a/startos/dependencies.ts b/startos/dependencies.ts new file mode 100644 index 0000000..6fe4126 --- /dev/null +++ b/startos/dependencies.ts @@ -0,0 +1,27 @@ +import { envFile } from './fileModels/env' +import { sdk } from './sdk' + +export const setDependencies = sdk.setupDependencies(async ({ effects }) => { + const configuredLnImplementation = await envFile + .read((e) => e.LNBITS_BACKEND_WALLET_CLASS) + .const(effects) + + if (configuredLnImplementation === 'LndRestWallet') { + return { + lnd: { + healthChecks: ['primary'], + kind: 'running', + versionRange: '>=0.19.2-beta:1-beta.1', + }, + } + } else if (configuredLnImplementation === 'CoreLightningWallet') { + return { + 'c-lightning': { + healthChecks: ['lightningd'], + kind: 'running', + versionRange: '>=25.5.0:1-alpha.1', + }, + } + } + return {} +}) diff --git a/startos/fileModels/env.ts b/startos/fileModels/env.ts new file mode 100644 index 0000000..d9c9d80 --- /dev/null +++ b/startos/fileModels/env.ts @@ -0,0 +1,130 @@ +import { matches, FileHelper } from '@start9labs/start-sdk' +const { object, string } = matches + +import { envDefaults } from '../utils' +import { literal, literals } from 'ts-matches' + +const { + HOST, + PORT, + FORWARDED_ALLOW_IPS, + DEBUG, + AUTH_ALLOWED_METHODS, + LNBITS_ALLOWED_USERS, + LNBITS_ADMIN_USERS, + LNBITS_ADMIN_EXTENSIONS, + LNBITS_ADMIN_UI, + LNBITS_DEFAULT_WALLET_NAME, + LNBITS_HIDE_API, + LNBITS_DISABLED_EXTENSIONS, + LNBITS_DATA_FOLDER, + LNBITS_FORCE_HTTPS, + LNBITS_RESERVE_FEE_MIN, + LNBITS_RESERVE_FEE_PERCENT, + LNBITS_SITE_TITLE, + LNBITS_SITE_TAGLINE, + LNBITS_SITE_DESCRIPTION, + LNBITS_THEME_OPTIONS, + LNBITS_CUSTOM_LOGO, + LNBITS_BACKEND_WALLET_CLASS, + LNBITS_ALLOWED_FUNDING_SOURCES, + CLIGHTNING_RPC, + LND_REST_ENDPOINT, + LND_REST_CERT, + LND_REST_MACAROON, +} = envDefaults + +const shape = object({ + HOST: literal(HOST).onMismatch(HOST), + PORT: literal(PORT).onMismatch(PORT), + + // uvicorn variable, allow https behind a proxy + FORWARDED_ALLOW_IPS: + literal(FORWARDED_ALLOW_IPS).onMismatch(FORWARDED_ALLOW_IPS), + + DEBUG: literals('true', 'false').optional().onMismatch(DEBUG), + + AUTH_ALLOWED_METHODS: string.onMismatch(AUTH_ALLOWED_METHODS), + + LNBITS_ALLOWED_USERS: string.optional().onMismatch(LNBITS_ALLOWED_USERS), + LNBITS_ADMIN_USERS: string.optional().onMismatch(LNBITS_ADMIN_USERS), + // Extensions only admin can access + LNBITS_ADMIN_EXTENSIONS: string + .optional() + .onMismatch(LNBITS_ADMIN_EXTENSIONS), + // Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available + LNBITS_ADMIN_UI: literals('true', 'false') + .optional() + .onMismatch(LNBITS_ADMIN_UI), + + LNBITS_DEFAULT_WALLET_NAME: string + .optional() + .onMismatch(LNBITS_DEFAULT_WALLET_NAME), + + // Ad space description + // LNBITS_AD_SPACE_TITLE="Supported by" + // csv ad space, format ";;, ;;", extensions can choose to honor + // LNBITS_AD_SPACE="" # csv ad image filepaths or urls, extensions can choose to honor + LNBITS_HIDE_API: literals('true', 'false') + .optional() + .onMismatch(LNBITS_HIDE_API), // Hides wallet api, extensions can choose to honor + + // Disable extensions for all users, use "all" to disable all extensions + LNBITS_DISABLED_EXTENSIONS: string + .optional() + .onMismatch(LNBITS_DISABLED_EXTENSIONS), + + // Database: to use SQLite, specify LNBITS_DATA_FOLDER + // to use PostgreSQL, specify LNBITS_DATABASE_URL=postgres://... + // to use CockroachDB, specify LNBITS_DATABASE_URL=cockroachdb://... + // for both PostgreSQL and CockroachDB, you'll need to install + // psycopg2 as an additional dependency + LNBITS_DATA_FOLDER: + literal(LNBITS_DATA_FOLDER).onMismatch(LNBITS_DATA_FOLDER), + // LNBITS_DATABASE_URL="postgres://user:password@host:port/databasename" + + LNBITS_FORCE_HTTPS: literals('true', 'false') + .optional() + .onMismatch(LNBITS_FORCE_HTTPS), + // LNBITS_SERVICE_FEE="0.0" + // value in millisats + LNBITS_RESERVE_FEE_MIN: string.optional().onMismatch(LNBITS_RESERVE_FEE_MIN), + // value in percent + LNBITS_RESERVE_FEE_PERCENT: string + .optional() + .onMismatch(LNBITS_RESERVE_FEE_PERCENT), + + // Change theme + LNBITS_SITE_TITLE: string.optional().onMismatch(LNBITS_SITE_TITLE), + LNBITS_SITE_TAGLINE: string.optional().onMismatch(LNBITS_SITE_TAGLINE), + LNBITS_SITE_DESCRIPTION: string + .optional() + .onMismatch(LNBITS_SITE_DESCRIPTION), + // Choose from mint, flamingo, freedom, salvador, autumn, monochrome, classic + LNBITS_THEME_OPTIONS: string.optional().onMismatch(LNBITS_THEME_OPTIONS), + LNBITS_CUSTOM_LOGO: string.optional().onMismatch(LNBITS_CUSTOM_LOGO), + + // Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet, LnTipsWallet + // LndRestWallet, CoreLightningWallet, LNbitsWallet, SparkWallet, FakeWallet, EclairWallet + LNBITS_BACKEND_WALLET_CLASS: literals( + 'LndRestWallet', + 'CoreLightningWallet', + ).onMismatch(LNBITS_BACKEND_WALLET_CLASS), + // VoidWallet is just a fallback that works without any actual Lightning capabilities, + // just so you can see the UI before dealing with this file. + + LNBITS_ALLOWED_FUNDING_SOURCES: literals( + 'LndRestWallet', + 'CoreLightningWallet', + ).onMismatch(LNBITS_ALLOWED_FUNDING_SOURCES), + + // CLightningWallet + CLIGHTNING_RPC: literal(CLIGHTNING_RPC).onMismatch(CLIGHTNING_RPC), + + // LndRestWallet + LND_REST_ENDPOINT: literal(LND_REST_ENDPOINT).onMismatch(LND_REST_ENDPOINT), + LND_REST_CERT: literal(LND_REST_CERT).onMismatch(LND_REST_CERT), + LND_REST_MACAROON: literal(LND_REST_MACAROON).onMismatch(LND_REST_MACAROON), +}) + +export const envFile = FileHelper.env('/media/startos/volumes/main/.env', shape) diff --git a/startos/index.ts b/startos/index.ts new file mode 100644 index 0000000..5bc2685 --- /dev/null +++ b/startos/index.ts @@ -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 './install/versionGraph' +export const manifest = buildManifest(versionGraph, sdkManifest) diff --git a/startos/init/index.ts b/startos/init/index.ts new file mode 100644 index 0000000..13eda04 --- /dev/null +++ b/startos/init/index.ts @@ -0,0 +1,18 @@ +import { sdk } from '../sdk' +import { setDependencies } from '../dependencies' +import { setInterfaces } from '../interfaces' +import { versionGraph } from '../install/versionGraph' +import { actions } from '../actions' +import { restoreInit } from '../backups' +import { taskSetLnImplementation } from './taskSetLnImplementation' + +export const init = sdk.setupInit( + restoreInit, + versionGraph, + setInterfaces, + setDependencies, + actions, + taskSetLnImplementation, +) + +export const uninit = sdk.setupUninit(versionGraph) diff --git a/startos/init/taskSetLnImplementation.ts b/startos/init/taskSetLnImplementation.ts new file mode 100644 index 0000000..f4827f0 --- /dev/null +++ b/startos/init/taskSetLnImplementation.ts @@ -0,0 +1,12 @@ +import { setLnImplementation } from '../actions/lightningImplementation' +import { sdk } from '../sdk' + +export const taskSetLnImplementation = sdk.setupOnInit( + async (effects, kind) => { + if (kind === 'install') { + await sdk.action.createOwnTask(effects, setLnImplementation, 'critical', { + reason: 'LNbits requires an underlying Lightning node!', + }) + } + }, +) diff --git a/startos/install/versionGraph.ts b/startos/install/versionGraph.ts new file mode 100644 index 0000000..2825e57 --- /dev/null +++ b/startos/install/versionGraph.ts @@ -0,0 +1,16 @@ +import { VersionGraph } from '@start9labs/start-sdk' +import { current, other } from './versions' +import { envFile } from '../fileModels/env' +import { envDefaults } from '../utils' + +export const versionGraph = VersionGraph.of({ + current, + other, + preInstall: async (effects) => { + const existingEnv = await envFile.read().once() + + if (!existingEnv) { + await envFile.write(effects, envDefaults) + } + }, +}) diff --git a/startos/install/versions/index.ts b/startos/install/versions/index.ts new file mode 100644 index 0000000..cb9b793 --- /dev/null +++ b/startos/install/versions/index.ts @@ -0,0 +1,2 @@ +export { v1_2_1_1 as current } from './v1_2_1_1' +export const other = [] diff --git a/startos/install/versions/v1_2_1_1.ts b/startos/install/versions/v1_2_1_1.ts new file mode 100644 index 0000000..9f0e5cf --- /dev/null +++ b/startos/install/versions/v1_2_1_1.ts @@ -0,0 +1,38 @@ +import { VersionInfo, IMPOSSIBLE } from '@start9labs/start-sdk' +import { envFile } from '../../fileModels/env' +import { envDefaults } from '../../utils' +import { load } from 'js-yaml' +import { readFile } from 'fs/promises' + +export const v1_2_1_1 = VersionInfo.of({ + version: '1.2.1:1', + releaseNotes: 'Revamped for StartOS 0.4.0', + migrations: { + up: async ({ effects }) => { + await envFile.write(effects, envDefaults) + try { + const configYaml = load( + await readFile( + '/media/startos/volumes/main/start9/config.yaml', + 'utf8', + ), + ) as { implementation: 'LndRestWallet' | 'CLightningWallet' } + + const configuredImplementation = + configYaml.implementation === 'CLightningWallet' + ? 'CoreLightningWallet' + : 'LndRestWallet' + + console.log('configuredImplementation', configuredImplementation) + + await envFile.merge(effects, { + LNBITS_BACKEND_WALLET_CLASS: configuredImplementation, + LNBITS_ALLOWED_FUNDING_SOURCES: configuredImplementation, + }) + } catch { + console.log('config.yaml not found') + } + }, + down: IMPOSSIBLE, + }, +}) diff --git a/startos/interfaces.ts b/startos/interfaces.ts new file mode 100644 index 0000000..392428a --- /dev/null +++ b/startos/interfaces.ts @@ -0,0 +1,24 @@ +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: 'Web UI', + id: 'ui', + description: 'The web interface of LNbits', + type: 'ui', + masked: false, + schemeOverride: null, + username: null, + path: '', + query: {}, + }) + + const uiReceipt = await uiMultiOrigin.export([ui]) + + return [uiReceipt] +}) diff --git a/startos/main.ts b/startos/main.ts new file mode 100644 index 0000000..d563d7f --- /dev/null +++ b/startos/main.ts @@ -0,0 +1,58 @@ +import { cwd } from 'process' +import { envFile } from './fileModels/env' +import { sdk } from './sdk' +import { mainMounts, uiPort } from './utils' + +export const main = sdk.setupMain(async ({ effects, started }) => { + /** + * ======================== Setup (optional) ======================== + * + * In this section, we fetch any resources or run any desired preliminary commands. + */ + console.info('Starting LNbits!') + + const configuredLnImplementation = await envFile + .read((e) => e.LNBITS_BACKEND_WALLET_CLASS) + .const(effects) + + const lnbitsSub = await sdk.SubContainer.of( + effects, + { imageId: 'lnbits' }, + mainMounts.mountDependency({ + dependencyId: + configuredLnImplementation === 'LndRestWallet' ? 'lnd' : 'c-lightning', + mountpoint: + configuredLnImplementation === 'LndRestWallet' + ? '/mnt/lnd' + : '/mnt/cln', + readonly: true, + subpath: null, + volumeId: 'main', + }), + 'lnbits-sub', + ) + + const env = await envFile.read().const(effects) + + /** + * ======================== Daemons ======================== + * + * In this section, we create one or more daemons that define the service runtime. + * + * Each daemon defines its own health check, which can optionally be exposed to the user. + */ + return sdk.Daemons.of(effects, started).addDaemon('primary', { + subcontainer: lnbitsSub, + exec: { command: ['poetry', 'run', 'lnbits'], env: env || {} }, + ready: { + display: 'Web Interface', + gracePeriod: 75_000, + fn: () => + sdk.healthCheck.checkPortListening(effects, uiPort, { + successMessage: 'The web interface is ready', + errorMessage: 'The web interface is not ready', + }), + }, + requires: [], + }) +}) diff --git a/startos/manifest.ts b/startos/manifest.ts new file mode 100644 index 0000000..481bf50 --- /dev/null +++ b/startos/manifest.ts @@ -0,0 +1,57 @@ +import { setupManifest } from '@start9labs/start-sdk' +import { SDKImageInputSpec } from '@start9labs/start-sdk/base/lib/types/ManifestTypes' + +const BUILD = process.env.BUILD || '' + +const architectures = + BUILD === 'x86_64' || BUILD === 'aarch64' ? [BUILD] : ['x86_64', 'aarch64'] + +export const manifest = setupManifest({ + id: 'lnbits', + title: 'LNbits', + license: 'mit', + wrapperRepo: 'https://github.com/Start9Labs/lnbits-startos', + upstreamRepo: 'https://github.com/Start9Labs/lnbits', + supportSite: 'https://github.com/lnbits/lnbits/issues', + marketingSite: 'https://lnbits.com/', + donationUrl: 'https://demo.lnbits.com/tipjar/DwaUiE4kBX6mUW6pj3X5Kg', + docsUrl: + 'https://github.com/Start9Labs/lnbits-startos/blob/master/instructions.md', + description: { + short: 'Free and open-source lightning-network wallet/accounts system.', + long: 'A very simple Python server that sits on top of any funding source, and can be used as an accounts system, extendable platform, development stack, fallback wallet or even instant wallet for LN demonstrations', + }, + volumes: ['main'], + images: { + lnbits: { + source: { + dockerBuild: { + dockerfile: 'Dockerfile', + workdir: '.', + }, + }, + arch: architectures, + } as SDKImageInputSpec, + }, + hardwareRequirements: {}, + alerts: { + install: null, + update: null, + uninstall: null, + restore: null, + start: null, + stop: null, + }, + dependencies: { + 'c-lightning': { + description: 'Optionally connect RTL to your CLN node.', + optional: true, + s9pk: 'https://github.com/Start9Labs/cln-startos/releases/download/v25.05.0.1-alpha.1/c-lightning.s9pk', + }, + lnd: { + description: 'Optionally connect RTL to your LND node.', + optional: true, + s9pk: 'https://github.com/Start9Labs/lnd-startos/releases/download/v0.19.2-beta.1-beta.1/lnd.s9pk', + }, + }, +}) diff --git a/startos/sdk.ts b/startos/sdk.ts new file mode 100644 index 0000000..04ae4b1 --- /dev/null +++ b/startos/sdk.ts @@ -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) diff --git a/startos/utils.ts b/startos/utils.ts new file mode 100644 index 0000000..4eb8fa9 --- /dev/null +++ b/startos/utils.ts @@ -0,0 +1,85 @@ +import { sdk } from './sdk' + +export const uiPort = 5000 +export const db = '/app/data/database.sqlite3' +export const randomPassword = { + charset: 'a-z,A-Z,1-9,+,/', + len: 22, +} + +export const mainMounts = sdk.Mounts.of().mountVolume({ + volumeId: 'main', + mountpoint: '/app/data', + readonly: false, + subpath: null, + type: 'directory', +}) + +export const envDefaults = { + HOST: 'lnbits.startos', + PORT: '5000', + + // uvicorn variable, allow https behind a proxy + FORWARDED_ALLOW_IPS: '*', + + DEBUG: 'false', + + AUTH_ALLOWED_METHODS: 'username-password', + LNBITS_ALLOWED_USERS: '', + LNBITS_ADMIN_USERS: '', + // Extensions only admin can access + LNBITS_ADMIN_EXTENSIONS: 'ngrok, admin', + // Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available + LNBITS_ADMIN_UI: 'true', + + LNBITS_DEFAULT_WALLET_NAME: 'LNbits wallet', + + // Ad space description + // LNBITS_AD_SPACE_TITLE="Supported by" + // csv ad space, format ";;, ;;", extensions can choose to honor + // LNBITS_AD_SPACE="" # csv ad image filepaths or urls, extensions can choose to honor + LNBITS_HIDE_API: 'false', // Hides wallet api, extensions can choose to honor + + // Disable extensions for all users, use "all" to disable all extensions + LNBITS_DISABLED_EXTENSIONS: 'amilk', + + // Database: to use SQLite, specify LNBITS_DATA_FOLDER + // to use PostgreSQL, specify LNBITS_DATABASE_URL=postgres://... + // to use CockroachDB, specify LNBITS_DATABASE_URL=cockroachdb://... + // for both PostgreSQL and CockroachDB, you'll need to install + // psycopg2 as an additional dependency + LNBITS_DATA_FOLDER: './data', + // LNBITS_DATABASE_URL="postgres://user:password@host:port/databasename" + + LNBITS_FORCE_HTTPS: 'false', + // LNBITS_SERVICE_FEE="0.0" + // value in millisats + LNBITS_RESERVE_FEE_MIN: '2000', + // value in percent + LNBITS_RESERVE_FEE_PERCENT: '1.0', + + // Change theme + LNBITS_SITE_TITLE: 'LNbits', + LNBITS_SITE_TAGLINE: 'free and open-source self-hosted lightning wallet', + LNBITS_SITE_DESCRIPTION: 'Made for you, hosted by you.', + // Choose from mint, flamingo, freedom, salvador, autumn, monochrome, classic + LNBITS_THEME_OPTIONS: + 'classic, bitcoin, freedom, mint, autumn, monochrome, salvador', + LNBITS_CUSTOM_LOGO: '', + + // Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet, LnTipsWallet + // LndRestWallet, CoreLightningWallet, LNbitsWallet, SparkWallet, FakeWallet, EclairWallet + LNBITS_BACKEND_WALLET_CLASS: 'LndRestWallet', + // VoidWallet is just a fallback that works without any actual Lightning capabilities, + // just so you can see the UI before dealing with this file. + + LNBITS_ALLOWED_FUNDING_SOURCES: 'LndRestWallet', + + // CLightningWallet + CLIGHTNING_RPC: '/mnt/cln/bitcoin/lightning-rpc', + + // LndRestWallet + LND_REST_ENDPOINT: 'https://lnd.startos:8080/', + LND_REST_CERT: '/mnt/lnd/tls.cert', + LND_REST_MACAROON: '/mnt/lnd/data/chain/bitcoin/mainnet/admin.macaroon', +} as const diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a2945a5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "include": ["startos/**/*.ts", "node_modules/**/startos"], + "compilerOptions": { + "target": "ES2018", + "module": "CommonJS", + "moduleResolution": "node", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true + } +} From ba111602bd0c3faff7296bafe7c6f6fbe73e0fea Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Wed, 20 Aug 2025 13:50:48 -0600 Subject: [PATCH 03/49] remove debug logging --- startos/actions/resetPassword.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/startos/actions/resetPassword.ts b/startos/actions/resetPassword.ts index 1e41af5..a9c78f4 100644 --- a/startos/actions/resetPassword.ts +++ b/startos/actions/resetPassword.ts @@ -21,7 +21,6 @@ export const resetPassword = sdk.Action.withoutInput( // the execution function async ({ effects }) => { const newPassword = utils.getDefaultString(randomPassword) - console.log('newPassword', newPassword) await sdk.SubContainer.withTemp( effects, @@ -35,24 +34,13 @@ export const resetPassword = sdk.Action.withoutInput( "select value from system_settings where id = 'super_user';", ]) - console.log( - 'superuserAccountId', - superuserAccountId.stdout.toString().trimEnd(), - ) - const newPasswordHash = await subc.execFail([ 'python3', '-c', `import bcrypt; print(bcrypt.hashpw('${newPassword}'.encode('utf-8'), bcrypt.gensalt()).decode('utf-8'))`, ]) - console.log( - 'newPasswordHash', - newPasswordHash.stdout.toString().trimEnd(), - ) - const currentTime = await subc.execFail(['date', '+%s']) - console.log('currentTime', currentTime.stdout.toString().trimEnd()) const res = await subc.execFail([ 'sqlite3', @@ -60,7 +48,6 @@ export const resetPassword = sdk.Action.withoutInput( `update accounts set password_hash = \"${newPasswordHash.stdout.toString().trimEnd()}\", updated_at = ${currentTime.stdout.toString().trimEnd()} where id = ${superuserAccountId.stdout.toString().trimEnd()};`, ]) - console.log('res', res) }, ) return { From fef0267a723dc47000d52f4071d5fcf53da6167f Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Wed, 20 Aug 2025 13:52:03 -0600 Subject: [PATCH 04/49] remove unused imports --- startos/actions/resetPassword.ts | 1 - startos/main.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/startos/actions/resetPassword.ts b/startos/actions/resetPassword.ts index a9c78f4..24bd4f2 100644 --- a/startos/actions/resetPassword.ts +++ b/startos/actions/resetPassword.ts @@ -1,4 +1,3 @@ -import { access } from 'fs/promises' import { sdk } from '../sdk' import { db, mainMounts, randomPassword } from '../utils' import { utils } from '@start9labs/start-sdk' diff --git a/startos/main.ts b/startos/main.ts index d563d7f..0163ee1 100644 --- a/startos/main.ts +++ b/startos/main.ts @@ -1,4 +1,3 @@ -import { cwd } from 'process' import { envFile } from './fileModels/env' import { sdk } from './sdk' import { mainMounts, uiPort } from './utils' From 1552eec31e9fac3c365f87bc8853392d0d2d797c Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Wed, 20 Aug 2025 13:52:44 -0600 Subject: [PATCH 05/49] add pre-release tag --- startos/install/versions/v1_2_1_1.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/startos/install/versions/v1_2_1_1.ts b/startos/install/versions/v1_2_1_1.ts index 9f0e5cf..bbc0b11 100644 --- a/startos/install/versions/v1_2_1_1.ts +++ b/startos/install/versions/v1_2_1_1.ts @@ -5,7 +5,7 @@ import { load } from 'js-yaml' import { readFile } from 'fs/promises' export const v1_2_1_1 = VersionInfo.of({ - version: '1.2.1:1', + version: '1.2.1:1-alpha.0', releaseNotes: 'Revamped for StartOS 0.4.0', migrations: { up: async ({ effects }) => { From 453127329306d434a1649fe449e0c32cb000a76f Mon Sep 17 00:00:00 2001 From: StuPleb Date: Tue, 26 Aug 2025 20:11:21 +0200 Subject: [PATCH 06/49] First pass LNbits docs integration (#52) * First pass LNbits docs integration * Missed referrence to Zeus (to make wording in both guides consistent) fixed --- docs/instructions.md | 44 ++++++++++++++++++++++ docs/wallet-integrations/alby-extension.md | 27 +++++++++++++ docs/wallet-integrations/bluewallet.md | 25 ++++++++++++ docs/wallet-integrations/zeus.md | 27 +++++++++++++ instructions.md | 18 --------- 5 files changed, 123 insertions(+), 18 deletions(-) create mode 100644 docs/instructions.md create mode 100644 docs/wallet-integrations/alby-extension.md create mode 100644 docs/wallet-integrations/bluewallet.md create mode 100644 docs/wallet-integrations/zeus.md delete mode 100644 instructions.md diff --git a/docs/instructions.md b/docs/instructions.md new file mode 100644 index 0000000..3e4a839 --- /dev/null +++ b/docs/instructions.md @@ -0,0 +1,44 @@ +# LNbits + +LNbits core is a powerful wallet accounts system and extendable platform that you can use to create secure sub-wallets sitting on top of a funding source such as LND or CLN (installed separately from the marketplace). This allows you to be an "Uncle Jim" to friends and family or to organize or limit access to funds. Extremely extensible, it can also provide a wide range of other features like connecting bolt cards or ATMs. See also [LNbits.com](https://LNbits.com/). + +## Getting Started +### The Lightning Network + +Bitcoin's Lightning Network is a second-layer scaling solution designed to enable faster and cheaper transactions on top of the Bitcoin blockchain. It allows users to create off-chain payment channels, which can facilitate multiple transactions without needing to record each one on the main blockchain. + +Before installing and using LNbits you should read and understand the extensive documentation for either [LND](https://github.com/Start9Labs/lnd-startos/blob/master/docs/instructions.md) or [CLN](https://github.com/Start9Labs/cln-startos/blob/master/docs/instructions.md), at least one of which you must install prior to using LNbits. + + +### First Use + +You'll need to first select an underlying Lightning Network implementation (see above) prior to starting the service for the first time. + +**NOTE:** If the LN implementation is changed after using LNBits this will delete all LNBits accounts and wallets related to the previously configured LN implementation! All LN funds will still be available on the underlying LN implementation. + +Once ready, and you start the service and launch the Web UI you will be prompted to set up a *Superuser account*. This is your principle admin account with full rights. As you are the administrator you may use the superuser account as you main account or you can later create additional accounts ranging from admin level to ordinary users. You can reset this password if you forget it in StartOS from `LNbits > Actions > Reset Password`. + +### Funding + +LNbits sits on top of your chosen Lightning Network funding source, but only has access to the funds you main available to it. This means if you have channels on your LN implementation with a total of 500,000 sats of liquidity on your side, you can add up to that amount on LNbits. You can do this in two ways:- + +1. Create an invoice in LNbits and pay yourself from the same wallet that is funding your LNbits instance +2. Manually choose to add funds by clicking on a wallet and then the "Credit/Debit" button. + +Adding 1000 sats to a wallet by either means does not alter the balance of the underling Lightning Network implementation. But should you create an invoice in LNbits and pay it from an outside source, again say 1000 sats, your underlying LN implementation would handle the process, liquidity on your LN implementation would increment by 1000 sats and the same 1000 sats would be credited to the LNbits wallet. + +**NOTE:** You could spend 100% of your liquidity on your LN implementation through some other integration method and have no funds to spend, yet LNbits and any users would be unaware. LNbits is merely an accounting system on top of your LN implementation. Remember: All wallets are ultimately bound by the capacity of your LN node. If one wallet is allocated 1000 sats but your underlying node only has 900 sats of outbound capacity, payments will simply fail. + + +## Backups + +When your server backs up LNbits, it takes a copy of your settings, your user accounts and their sub-wallets. It does NOT back up and funds, just the accounting for individual users. Actual funds remain on the underlying LN implementation (i.e. LND or CLN). + + +## Interacting with LNbits and connecting wallets + +LNbits wallets can be used as real wallets and have wallet software manage them. This is incredibly useful and powerful, allowing you to safely segregate funds or to only carry access to small amounts of your LN channel capacity in person. + +- [Alby Browser Extension](wallet-integrations/alby-extension.md) +- [BlueWallet](wallet-integrations/bluewallet.md) +- [Zeus](wallet-integrations/zeus.md) \ No newline at end of file diff --git a/docs/wallet-integrations/alby-extension.md b/docs/wallet-integrations/alby-extension.md new file mode 100644 index 0000000..0b0c6c6 --- /dev/null +++ b/docs/wallet-integrations/alby-extension.md @@ -0,0 +1,27 @@ +# Alby Browser Extension with LNbits + +Alby is a browser extension that can be connected to your lightning node a number of ways. This guide will go over connecting Alby to your **LNbits wallet** which allows allocation of funds. + + +**NOTE:** This guide assumes you have already setup LNbits as per [this guide](../instructions.md). + + +1. Download the Alby extension by visiting the [Alby Github](https://github.com/getAlby/lightning-browser-extension#installation), selecting your browser, and installing. + +1. Create a strong "unlock" password and store it somewhere safe, like your Vaultwarden password manager. + +1. On the next screen, in **Bring Your Own Wallet**, click **Find Your Wallet**. + +1. Click **StartOS** first, then **LNbits**. + +1. You will be asked to add the *LNbits Admin Key*. + +1. Head back to LNbits and select the wallet you want to use then click on the arrow to the right of **Node URL, API keys and API docs** to expand the details. + +1. Copy the **Admin key** and paste it into Alby. + +1. If you have opened the LNbits web UI via your preferred interface (IP, .local, clearnet) you can copy the Node URL from the same section of LNbits. Otherwise head back to your StartOS LNbits service page, the Interfaces section, and choose the one you prefer. + +1. Click **Continue**. Once the connection is completed you will see a success page that displays the balance of your LNbits wallet. + +You’re now setup with Alby and LNbits! diff --git a/docs/wallet-integrations/bluewallet.md b/docs/wallet-integrations/bluewallet.md new file mode 100644 index 0000000..d7c123a --- /dev/null +++ b/docs/wallet-integrations/bluewallet.md @@ -0,0 +1,25 @@ +# BlueWallet with LNbits + + +**WARNING:** This is not the same as connecting BlueWallet directly to your lightning node - using LNbits allows us to allocate a specific amount of funds to BlueWallet instead of giving it full access to your lightning node. We can also use LNbits to permit BlueWallet to **just receive** satoshis, or the ability to both **receive and spend** satoshis. + +**NOTE:** This guide assumes you have already setup LNbits as per [this guide](../instructions.md). + +**WARNING:** This will not work with CLN as your underlying LN implementation! + + +1. BlueWallet requires that we use the LndHub extension in order to connect to LNbits. To install this, click **Extensions**, in the **ALL** tab find LndHub, click **MANAGE**, find the latest version and click **INSTALL**. Once installed, click **ENABLE** + +1. Click **OPEN** *or* **LndHub** under *Extensions* + +1. Make sure the wallet you want to use is selected below the two QR codes, we will use these in a moment. + +1. Install [BlueWallet](https://bluewallet.io/) if you haven't already. + +1. Go to Import wallet on BlueWallet, then click on *Scan or import a file*. + +1. *If you only want this wallet to be able to RECEIVE PAYMENTS, scan the "Invoice" QR code* + *If you are happy for this wallet to be able to both receive and MAKE payments scan the "Admin" QR code* + + +Congratulations! BlueWallet is set up and ready to use lightning via your own lightning node - furthermore it will only be able to use your node in the way you allow it, via LNbits. \ No newline at end of file diff --git a/docs/wallet-integrations/zeus.md b/docs/wallet-integrations/zeus.md new file mode 100644 index 0000000..0af9bea --- /dev/null +++ b/docs/wallet-integrations/zeus.md @@ -0,0 +1,27 @@ +# Zeus with LNbits + +**WARNING:** This is not the same as connecting Zeus directly to your lightning node - using LNbits allows us to allocate a specific amount of funds to Zeus instead of giving it full access to your lightning node. We can also use LNbits to permit Zeus to **just receive** satoshis, or the ability to both **receive and spend** satoshis. + +**NOTE:** This guide assumes you have already setup LNbits as per [this guide](../instructions.md). + +**WARNING:** This will not work with CLN as your underlying LN implementation! + + +1. Zeus requires that we use the LndHub extension in order to connect to LNbits. To install this, click **Extensions**, in the **ALL** tab find LndHub, click **MANAGE**, find the latest version and click **INSTALL**. Once installed, click **ENABLE** + +1. Click **OPEN** *or* **LndHub** under *Extensions* + +1. Make sure the wallet you want to use is selected below the two QR codes, we will use these in a moment. + + +1. Install [Zeus](https://zeusln.app/) if you haven't already. + +1. In Zeus, if you are using it for the first time, tap "Scan node config". Allow camera access, scan the QR code, and then tap 'Save node config'. Zeus will fill in your node details based on the information in the QR code. If you already have other nodes configured in Zeus, go to Settings > Connect a node > + . But before you scan anything... + +1. *If you only want this wallet to be able to RECEIVE PAYMENTS, scan the "Invoice" QR code* + *If you are happy for this wallet to be able to both receive and MAKE payments scan the "Admin" QR code* + +1. Once scanned, name the wallet if you wish, then hit **SAVE NODE CONFIG**. + + +Congratulations! Zeus is set up and ready to use lightning via your own lightning node - furthermore it will only be able to use your node in the way you allow it, via LNbits. diff --git a/instructions.md b/instructions.md deleted file mode 100644 index 1def63c..0000000 --- a/instructions.md +++ /dev/null @@ -1,18 +0,0 @@ -# Overview - -LNbits is a robust and versatile platform, serving as a comprehensive hub for Lightning Network functionality. For a complete list of its extensive features and detailed guides, please visit [LNbits.com](https://LNbits.com/). - -## Using LNbits - -To use LNbits, simply click `LAUNCH UI` where you will be prompted to login or register. If `user-id-only` is enabled as an 'Authorization Method' (not recommended for security) StartOS will automatically save your wallet URLs and display them in the `Properties` page of your LNbits service dashboard. If an existing LNbits account is already logged in, `LAUNCH UI` will open that account instead of prompting the user to login or register. If an account is already logged in and you would like to register another account, you will first need to log out of the logged in account. As a corollary, only one LNbits account can be logged into a given browser at a time. If a LNbits account is already logged in and the URL for a different account is opened you may encounter the error `Wallet not found` or the previously logged in account may be displayed - in either case hard refreshing the page will log in the account of the URL entered and log out the other account. - -## Superuser -LNbits includes a `Superuser Account` which can also be found in `Properties` along with the default username and password. This account can be used to manage the server, allowing the user to add extensions, topup wallets, etc. - -The `Superuser Account` can also change the authentication required to access accounts. By default authentication is not required for users who updated to 0.12.2 from an earlier version, meaning accounts remain accessible via the URLs found in properties; accounts can also be accessed using username/password (provided these have been set). For fresh installations >= 0.12.2 username and password are required by default. Allowed authentication methods can be updated by opening the Superuser account and navivating to `Server` > `Security`. - -**Warning** If authentication previously allowed the `user-id-only` method is updated to disallow this method, any accounts which have not yet setup a username and password will be *inaccessible* until authentication is reverted to include the `user-id-only` method. Before changing the allowed authentication methods, it is highly recommended to ensure any existing accounts have been updated to include both a username and a password; login credentials should be stored securely (i.e. within Vaultwarden). - -**If the Superuser password is updated via the LNbits UI, make sure this password is stored securely as it will *NOT* be updated in `Properties`** - -Documentation for using LNbits can be found [here](https://docs.start9.com/0.3.5.x/service-guides/lightning/connecting-lnbits#connecting-lnbits) From 4a825af2f664cefe6d4a1c242363f2ee1727bff5 Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Wed, 3 Sep 2025 14:25:23 -0600 Subject: [PATCH 07/49] add username to resetPassword ActionResult --- startos/actions/resetPassword.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/startos/actions/resetPassword.ts b/startos/actions/resetPassword.ts index 24bd4f2..d0d10bf 100644 --- a/startos/actions/resetPassword.ts +++ b/startos/actions/resetPassword.ts @@ -20,8 +20,7 @@ export const resetPassword = sdk.Action.withoutInput( // the execution function async ({ effects }) => { const newPassword = utils.getDefaultString(randomPassword) - - await sdk.SubContainer.withTemp( + const res = await sdk.SubContainer.withTemp( effects, { imageId: 'lnbits' }, mainMounts, @@ -41,18 +40,23 @@ export const resetPassword = sdk.Action.withoutInput( const currentTime = await subc.execFail(['date', '+%s']) - const res = await subc.execFail([ + await subc.execFail([ 'sqlite3', db, `update accounts set password_hash = \"${newPasswordHash.stdout.toString().trimEnd()}\", updated_at = ${currentTime.stdout.toString().trimEnd()} where id = ${superuserAccountId.stdout.toString().trimEnd()};`, ]) + return await subc.execFail([ + 'sqlite3', + db, + `select username from accounts where id = ${superuserAccountId.stdout.toString().trimEnd()};`, + ]) }, ) return { version: '1', title: 'Success', - message: 'The Super User new password is below', + message: `The new Super User password for '${res.stdout}' is below`, result: { type: 'single', value: newPassword, From 0d6d597a9f378b62dff809e7644c34f72a273fae Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Wed, 24 Sep 2025 16:44:43 -0600 Subject: [PATCH 08/49] beta.41 and npm update --- package-lock.json | 26 +++++++++++++------------- package.json | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7dcba4c..49a921e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "hello-world-startos", "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.36", + "@start9labs/start-sdk": "^0.4.0-beta.41", "js-yaml": "^4.1.0" }, "devDependencies": { @@ -51,9 +51,9 @@ } }, "node_modules/@start9labs/start-sdk": { - "version": "0.4.0-beta.36", - "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.36.tgz", - "integrity": "sha512-26C1NGJBy/yubp88SriuX2wfVDAM6/1rDx47wdypR2KEqGzhOrMRQC5hJDAMU8BeRAiL6ED8/Ca9CP+5+8BBBw==", + "version": "0.4.0-beta.41", + "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.41.tgz", + "integrity": "sha512-nbr6fT8qtAr04lkFBMnUARAAVrTYdjoXA61j/1IzIvA9vkrkras985SE3YSAkgQPYfaAI27wLHQcAn8U0Kk01g==", "license": "MIT", "dependencies": { "@iarna/toml": "^3.0.0", @@ -82,9 +82,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.17.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.17.2.tgz", - "integrity": "sha512-gL6z5N9Jm9mhY+U2KXZpteb+09zyffliRkZyZOHODGATyC5B1Jt/7TzuuiLkFsSUMLbS1OLmlj/E+/3KF4Q/4w==", + "version": "22.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.6.tgz", + "integrity": "sha512-r8uszLPpeIWbNKtvWRt/DbVi5zbqZyj1PTmhRMqBMvDnaz1QpmSKujUtJLrqGZeoM8v72MfYggDceY4K1itzWQ==", "dev": true, "license": "MIT", "dependencies": { @@ -92,9 +92,9 @@ } }, "node_modules/@vercel/ncc": { - "version": "0.38.3", - "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.38.3.tgz", - "integrity": "sha512-rnK6hJBS6mwc+Bkab+PGPs9OiS0i/3kdTO+CkI8V0/VrW3vmz7O2Pxjw/owOlmo6PKEIxRSeZKv/kuL9itnpYA==", + "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": { @@ -148,9 +148,9 @@ } }, "node_modules/mime": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.7.tgz", - "integrity": "sha512-2OfDPL+e03E0LrXaGYOtTFIYhiuzep94NSsuhrNULq+stylcJedcHdzHtz0atMUuGwJfFYs0YL5xeC/Ca2x0eQ==", + "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" ], diff --git a/package.json b/package.json index 7dd0d36..eac0029 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "check": "tsc --noEmit" }, "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.36", + "@start9labs/start-sdk": "^0.4.0-beta.41", "js-yaml": "^4.1.0" }, "devDependencies": { From dfd71fe3962e3bba893f75a12b432020ed339db7 Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Wed, 24 Sep 2025 16:47:26 -0600 Subject: [PATCH 09/49] fix upstream repo link --- startos/manifest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/startos/manifest.ts b/startos/manifest.ts index 481bf50..fd0bbe9 100644 --- a/startos/manifest.ts +++ b/startos/manifest.ts @@ -11,7 +11,7 @@ export const manifest = setupManifest({ title: 'LNbits', license: 'mit', wrapperRepo: 'https://github.com/Start9Labs/lnbits-startos', - upstreamRepo: 'https://github.com/Start9Labs/lnbits', + upstreamRepo: 'https://github.com/lnbits/lnbits', supportSite: 'https://github.com/lnbits/lnbits/issues', marketingSite: 'https://lnbits.com/', donationUrl: 'https://demo.lnbits.com/tipjar/DwaUiE4kBX6mUW6pj3X5Kg', From fed37a0d17ff67a84180efc68c2b27d3b8545f7f Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Wed, 24 Sep 2025 16:47:46 -0600 Subject: [PATCH 10/49] bump deps to latest --- startos/dependencies.ts | 4 ++-- startos/manifest.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/startos/dependencies.ts b/startos/dependencies.ts index 6fe4126..3b0d093 100644 --- a/startos/dependencies.ts +++ b/startos/dependencies.ts @@ -11,7 +11,7 @@ export const setDependencies = sdk.setupDependencies(async ({ effects }) => { lnd: { healthChecks: ['primary'], kind: 'running', - versionRange: '>=0.19.2-beta:1-beta.1', + versionRange: '>=0.19.3-beta:1-beta.0', }, } } else if (configuredLnImplementation === 'CoreLightningWallet') { @@ -19,7 +19,7 @@ export const setDependencies = sdk.setupDependencies(async ({ effects }) => { 'c-lightning': { healthChecks: ['lightningd'], kind: 'running', - versionRange: '>=25.5.0:1-alpha.1', + versionRange: '>=v25.09.0.1-beta.0', }, } } diff --git a/startos/manifest.ts b/startos/manifest.ts index fd0bbe9..8438590 100644 --- a/startos/manifest.ts +++ b/startos/manifest.ts @@ -46,12 +46,12 @@ export const manifest = setupManifest({ 'c-lightning': { description: 'Optionally connect RTL to your CLN node.', optional: true, - s9pk: 'https://github.com/Start9Labs/cln-startos/releases/download/v25.05.0.1-alpha.1/c-lightning.s9pk', + s9pk: 'https://github.com/Start9Labs/cln-startos/releases/download/v25.09.0.1-beta.0/c-lightning.s9pk', }, lnd: { description: 'Optionally connect RTL to your LND node.', optional: true, - s9pk: 'https://github.com/Start9Labs/lnd-startos/releases/download/v0.19.2-beta.1-beta.1/lnd.s9pk', + s9pk: 'https://github.com/Start9Labs/lnd-startos/releases/download/v0.19.2-beta.1-beta.2/lnd.s9pk', }, }, }) From c501c46e029f144358f3a23cc4d3ba186cfa37b3 Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Wed, 24 Sep 2025 16:50:31 -0600 Subject: [PATCH 11/49] latest CI workflows --- .github/workflows/buildService.yml | 38 +++++++++++++ .github/workflows/releaseService.yml | 83 ++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 .github/workflows/buildService.yml create mode 100644 .github/workflows/releaseService.yml diff --git a/.github/workflows/buildService.yml b/.github/workflows/buildService.yml new file mode 100644 index 0000000..06432ce --- /dev/null +++ b/.github/workflows/buildService.yml @@ -0,0 +1,38 @@ +name: Build Service + +on: + workflow_dispatch: + pull_request: + paths-ignore: ['*.md'] + branches: ['main', 'master', 'update/040'] + push: + paths-ignore: ['*.md'] + branches: ['main', 'master', 'update/040'] + +jobs: + BuildPackage: + runs-on: ubuntu-latest + steps: + - name: Prepare StartOS SDK + uses: start9labs/sdk@v2 + + - name: Checkout services repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Build the service package + id: build + run: | + start-cli init + RUST_LOG=debug RUST_BACKTRACE=1 make + PACKAGE_ID=$(start-cli s9pk inspect *.s9pk manifest | jq -r '.id') + echo "package_id=${PACKAGE_ID}" >> $GITHUB_ENV + printf "\n SHA256SUM: $(sha256sum ${PACKAGE_ID}.s9pk) \n" + shell: bash + + - name: Upload .s9pk + uses: actions/upload-artifact@v4 + with: + name: ${{ env.package_id }}.s9pk + path: ./${{ env.package_id }}.s9pk diff --git a/.github/workflows/releaseService.yml b/.github/workflows/releaseService.yml new file mode 100644 index 0000000..99fa697 --- /dev/null +++ b/.github/workflows/releaseService.yml @@ -0,0 +1,83 @@ +name: Release Service + +on: + push: + tags: + - 'v*.*' + +jobs: + ReleasePackage: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Prepare StartOS SDK + uses: start9labs/sdk@v2 + + - name: Checkout services repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Build the service package + id: build + env: + S9DEVKEY: ${{ secrets.S9DEVKEY }} + run: | + start-cli init + if [[ -n "$S9DEVKEY" ]]; then + echo "Using developer key from secrets to sign the package." + printf '%s' "$S9DEVKEY" > ~/.startos/developer.key.pem + else + echo "Using newly generated developer key to sign the package." + fi + RUST_LOG=debug RUST_BACKTRACE=1 make + sleep 2 + MANIFEST_JSON=$(start-cli s9pk inspect *.s9pk manifest) + PACKAGE_ID=$(echo "$MANIFEST_JSON" | jq -r '.id') + PACKAGE_TITLE=$(echo "$MANIFEST_JSON" | jq -r '.title') + echo "package_id=${PACKAGE_ID}" >> $GITHUB_ENV + echo "package_title=${PACKAGE_TITLE}" >> $GITHUB_ENV + printf "\n SHA256SUM: $(sha256sum ${PACKAGE_ID}.s9pk) \n" + shell: bash + + - name: Generate sha256 checksum + run: | + sha256sum ${{ env.package_id }}.s9pk > ${{ env.package_id }}.s9pk.sha256 + shell: bash + + - name: Generate changelog + run: | + echo "## What's Changed" > change-log.txt + echo "" >> change-log.txt + RELEASE_NOTES=$(start-cli s9pk inspect *.s9pk manifest | jq -r '.releaseNotes') + echo "${RELEASE_NOTES}" >> change-log.txt + echo "## SHA256 Hash" >> change-log.txt + echo '```' >> change-log.txt + sha256sum ${{ env.package_id }}.s9pk >> change-log.txt + echo '```' >> change-log.txt + shell: bash + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.ref_name }} + name: ${{ env.package_title }} ${{ github.ref_name }} + prerelease: true + body_path: change-log.txt + files: | + ./${{ env.package_id }}.s9pk + ./${{ env.package_id }}.s9pk.sha256 + + - name: Publish to Registry + env: + S9DEVKEY: ${{ secrets.S9DEVKEY }} + S9REGISTRY: ${{ secrets.S9REGISTRY }} + run: | + if [[ -z "$S9DEVKEY" || -z "$S9REGISTRY" ]]; then + echo "Publish skipped: One or both of S9DEVKEY and S9REGISTRY secrets are not set." + else + echo "Publishing package to registry..." + start-cli --registry https://$S9REGISTRY registry package add ${{ env.package_id }}.s9pk ${{ github.server_url }}/${{ github.repository }}/releases/download/${{ github.ref_name }}/${{ env.package_id }}.s9pk + fi + shell: bash From ee820bd17da74f6fb748e06694893e0d4430468e Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Wed, 24 Sep 2025 16:51:07 -0600 Subject: [PATCH 12/49] latest makefile --- Makefile | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 94ca408..3a64f2b 100644 --- a/Makefile +++ b/Makefile @@ -1,31 +1,33 @@ PACKAGE_ID := $(shell awk -F"'" '/id:/ {print $$2}' startos/manifest.ts) INGREDIENTS := $(shell start-cli s9pk list-ingredients 2>/dev/null) -CMD_ARCH_GOAL := $(filter aarch64 x86_64, $(MAKECMDGOALS)) +CMD_ARCH_GOAL := $(filter aarch64 x86_64 arm x86, $(MAKECMDGOALS)) ifeq ($(CMD_ARCH_GOAL),) BUILD := universal S9PK := $(PACKAGE_ID).s9pk else - BUILD := $(firstword $(CMD_ARCH_GOAL)) + RAW_ARCH := $(firstword $(CMD_ARCH_GOAL)) + ACTUAL_ARCH := $(patsubst x86,x86_64,$(patsubst arm,aarch64,$(RAW_ARCH))) + BUILD := $(ACTUAL_ARCH) S9PK := $(PACKAGE_ID)_$(BUILD).s9pk endif -.PHONY: all aarch64 x86_64 clean install check-deps check-init package ingredients +.PHONY: all aarch64 x86_64 arm x86 clean install check-deps check-init package ingredients .DELETE_ON_ERROR: define SUMMARY @manifest=$$(start-cli s9pk inspect $(1) manifest); \ size=$$(du -h $(1) | awk '{print $$1}'); \ - title=$$(echo $$manifest | jq -r .title); \ - version=$$(echo $$manifest | jq -r .version); \ - arches=$$(echo $$manifest | jq -r '.hardwareRequirements.arch | join(", ")'); \ - sdkv=$$(echo $$manifest | jq -r .sdkVersion); \ - gitHash=$$(echo "$$manifest" | jq -r .gitHash | sed -E 's/(.*-modified)$$/\x1b[0;31m\1\x1b[0m/'); \ - echo ""; \ - echo "\033[1;32m✅ Build Complete!\033[0m"; \ - echo ""; \ - echo "\033[1;37m📦 $$title\033[0m \033[36mv$$version\033[0m"; \ - echo "───────────────────────────────"; \ + title=$$(printf '%s' "$$manifest" | jq -r .title); \ + version=$$(printf '%s' "$$manifest" | jq -r .version); \ + arches=$$(printf '%s' "$$manifest" | jq -r '.hardwareRequirements.arch | 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"; \ @@ -40,6 +42,9 @@ all: $(PACKAGE_ID).s9pk $(BUILD): $(PACKAGE_ID)_$(BUILD).s9pk $(call SUMMARY,$(S9PK)) +x86: x86_64 +arm: aarch64 + $(S9PK): $(INGREDIENTS) .git/HEAD .git/index @$(MAKE) --no-print-directory ingredients @echo " Packing '$(S9PK)'..." From f099a1737f028699bcc8326097224f2e16f356d3 Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Wed, 24 Sep 2025 16:56:59 -0600 Subject: [PATCH 13/49] bump pre-release tag --- startos/install/versions/index.ts | 6 ++++-- startos/install/versions/v1_2_1_1-alpha.1.ts | 9 +++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 startos/install/versions/v1_2_1_1-alpha.1.ts diff --git a/startos/install/versions/index.ts b/startos/install/versions/index.ts index cb9b793..32017e2 100644 --- a/startos/install/versions/index.ts +++ b/startos/install/versions/index.ts @@ -1,2 +1,4 @@ -export { v1_2_1_1 as current } from './v1_2_1_1' -export const other = [] +import { v1_2_1_1 } from './v1_2_1_1' +export { v1_2_1_1_alpha1 as current } from './v1_2_1_1-alpha.1' + +export const other = [v1_2_1_1] diff --git a/startos/install/versions/v1_2_1_1-alpha.1.ts b/startos/install/versions/v1_2_1_1-alpha.1.ts new file mode 100644 index 0000000..ddb247a --- /dev/null +++ b/startos/install/versions/v1_2_1_1-alpha.1.ts @@ -0,0 +1,9 @@ +import { VersionInfo, IMPOSSIBLE } from '@start9labs/start-sdk' + +export const v1_2_1_1_alpha1 = VersionInfo.of({ + version: '1.2.1:1-alpha.1', + releaseNotes: 'Revamped for StartOS 0.4.0', + migrations: { + down: IMPOSSIBLE, + }, +}) From 6748ab9afb332e74c5ecf97449c97c13c4961243 Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Wed, 24 Sep 2025 17:17:28 -0600 Subject: [PATCH 14/49] add ABOUT.md to assets dir --- assets/ABOUT.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 assets/ABOUT.md diff --git a/assets/ABOUT.md b/assets/ABOUT.md new file mode 100644 index 0000000..4fbfc10 --- /dev/null +++ b/assets/ABOUT.md @@ -0,0 +1 @@ +Use the `/assets` directory to include additional files or scripts needed by your service. From 6f9c2a93dab728decfd858b40ef9d4e1f53973c1 Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Thu, 25 Sep 2025 11:55:37 -0600 Subject: [PATCH 15/49] use latest lnd s9pk in manifest --- startos/manifest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/startos/manifest.ts b/startos/manifest.ts index 8438590..3fc6c15 100644 --- a/startos/manifest.ts +++ b/startos/manifest.ts @@ -51,7 +51,7 @@ export const manifest = setupManifest({ lnd: { description: 'Optionally connect RTL to your LND node.', optional: true, - s9pk: 'https://github.com/Start9Labs/lnd-startos/releases/download/v0.19.2-beta.1-beta.2/lnd.s9pk', + s9pk: 'https://github.com/Start9Labs/lnd-startos/releases/download/v0.19.3-beta.1-beta.0/lnd.s9pk', }, }, }) From 3c7a65778c1c2c77097862889e26a36a10be59a4 Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Fri, 31 Oct 2025 11:49:47 -0600 Subject: [PATCH 16/49] 1.3.1 --- Dockerfile | 4 ++-- startos/install/versions/index.ts | 2 +- .../versions/{v1_2_1_1-alpha.1.ts => v1_3_1_1-alpha.0.ts} | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename startos/install/versions/{v1_2_1_1-alpha.1.ts => v1_3_1_1-alpha.0.ts} (66%) diff --git a/Dockerfile b/Dockerfile index 45a7f19..6f06bb2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM lnbits/lnbits:v1.2.1 AS builder +FROM lnbits/lnbits:v1.3.1 AS builder # arm64 or amd64 ARG PLATFORM @@ -6,7 +6,7 @@ ARG PLATFORM RUN apt-get update && apt-get install -y bash curl sqlite3 tini --no-install-recommends RUN curl -sS https://webi.sh/yq | sh -FROM lnbits/lnbits:v1.2.1 AS final +FROM lnbits/lnbits:v1.3.1 AS final COPY --from=builder /usr/bin/tini /usr/bin/tini COPY --from=builder /usr/bin/sqlite3 /usr/bin/sqlite3 diff --git a/startos/install/versions/index.ts b/startos/install/versions/index.ts index 32017e2..4292c1f 100644 --- a/startos/install/versions/index.ts +++ b/startos/install/versions/index.ts @@ -1,4 +1,4 @@ import { v1_2_1_1 } from './v1_2_1_1' -export { v1_2_1_1_alpha1 as current } from './v1_2_1_1-alpha.1' +export { v1_3_1_1_alpha0 as current } from './v1_3_1_1-alpha.0' export const other = [v1_2_1_1] diff --git a/startos/install/versions/v1_2_1_1-alpha.1.ts b/startos/install/versions/v1_3_1_1-alpha.0.ts similarity index 66% rename from startos/install/versions/v1_2_1_1-alpha.1.ts rename to startos/install/versions/v1_3_1_1-alpha.0.ts index ddb247a..67c81d7 100644 --- a/startos/install/versions/v1_2_1_1-alpha.1.ts +++ b/startos/install/versions/v1_3_1_1-alpha.0.ts @@ -1,7 +1,7 @@ import { VersionInfo, IMPOSSIBLE } from '@start9labs/start-sdk' -export const v1_2_1_1_alpha1 = VersionInfo.of({ - version: '1.2.1:1-alpha.1', +export const v1_3_1_1_alpha0 = VersionInfo.of({ + version: '1.3.1:1-alpha.0', releaseNotes: 'Revamped for StartOS 0.4.0', migrations: { down: IMPOSSIBLE, From 3053c52c9e52040580642d1cb3292c807e0b252e Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Fri, 31 Oct 2025 11:50:47 -0600 Subject: [PATCH 17/49] remove Old directory --- Old/.env.example | 114 - Old/Makefile | 70 - Old/actions/reset-pass.sh | 27 - Old/check-web.sh | 11 - Old/docker_entrypoint.sh | 216 -- Old/manifest.yaml | 143 - Old/scripts/bundle.ts | 6 - Old/scripts/deps.ts | 1 - Old/scripts/embassy.js | 4014 -------------------------- Old/scripts/embassy.ts | 5 - Old/scripts/services/getConfig.ts | 36 - Old/scripts/services/healthChecks.ts | 5 - Old/scripts/services/migrations.ts | 37 - Old/scripts/services/properties.ts | 3 - Old/scripts/services/setConfig.ts | 16 - Old/workflows/buildService.yml | 36 - Old/workflows/releaseService.yml | 71 - 17 files changed, 4811 deletions(-) delete mode 100644 Old/.env.example delete mode 100644 Old/Makefile delete mode 100644 Old/actions/reset-pass.sh delete mode 100644 Old/check-web.sh delete mode 100755 Old/docker_entrypoint.sh delete mode 100644 Old/manifest.yaml delete mode 100644 Old/scripts/bundle.ts delete mode 100644 Old/scripts/deps.ts delete mode 100644 Old/scripts/embassy.js delete mode 100644 Old/scripts/embassy.ts delete mode 100644 Old/scripts/services/getConfig.ts delete mode 100644 Old/scripts/services/healthChecks.ts delete mode 100644 Old/scripts/services/migrations.ts delete mode 100644 Old/scripts/services/properties.ts delete mode 100644 Old/scripts/services/setConfig.ts delete mode 100644 Old/workflows/buildService.yml delete mode 100644 Old/workflows/releaseService.yml diff --git a/Old/.env.example b/Old/.env.example deleted file mode 100644 index e62a34c..0000000 --- a/Old/.env.example +++ /dev/null @@ -1,114 +0,0 @@ -HOST=127.0.0.1 -PORT=5000 - -# uvicorn variable, allow https behind a proxy -FORWARDED_ALLOW_IPS="*" - -DEBUG=false - -LNBITS_ALLOWED_USERS="" -LNBITS_ADMIN_USERS="" -# Extensions only admin can access -LNBITS_ADMIN_EXTENSIONS="ngrok, admin" -# Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available -LNBITS_ADMIN_UI=true - -LNBITS_DEFAULT_WALLET_NAME="LNbits wallet" - - -# Ad space description -# LNBITS_AD_SPACE_TITLE="Supported by" -# csv ad space, format ";;, ;;", extensions can choose to honor -# LNBITS_AD_SPACE="" # csv ad image filepaths or urls, extensions can choose to honor -LNBITS_HIDE_API=false # Hides wallet api, extensions can choose to honor - -# Disable extensions for all users, use "all" to disable all extensions -LNBITS_DISABLED_EXTENSIONS="amilk" - -# Database: to use SQLite, specify LNBITS_DATA_FOLDER -# to use PostgreSQL, specify LNBITS_DATABASE_URL=postgres://... -# to use CockroachDB, specify LNBITS_DATABASE_URL=cockroachdb://... -# for both PostgreSQL and CockroachDB, you'll need to install -# psycopg2 as an additional dependency -LNBITS_DATA_FOLDER="./data" -# LNBITS_DATABASE_URL="postgres://user:password@host:port/databasename" - -LNBITS_FORCE_HTTPS=false -# LNBITS_SERVICE_FEE="0.0" -# value in millisats -LNBITS_RESERVE_FEE_MIN=2000 -# value in percent -LNBITS_RESERVE_FEE_PERCENT=1.0 - -# Change theme -LNBITS_SITE_TITLE="LNbits" -LNBITS_SITE_TAGLINE="free and open-source self-hosted lightning wallet" -LNBITS_SITE_DESCRIPTION="Made for you, hosted by you." -# Choose from mint, flamingo, freedom, salvador, autumn, monochrome, classic -LNBITS_THEME_OPTIONS="classic, bitcoin, freedom, mint, autumn, monochrome, salvador" -LNBITS_CUSTOM_LOGO="" - -# Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet, LnTipsWallet -# LndRestWallet, CoreLightningWallet, LNbitsWallet, SparkWallet, FakeWallet, EclairWallet -LNBITS_BACKEND_WALLET_CLASS=LndRestWallet -# VoidWallet is just a fallback that works without any actual Lightning capabilities, -# just so you can see the UI before dealing with this file. - -# this will be set from docker_entrypoint.sh -LNBITS_ALLOWED_FUNDING_SOURCES="" - -# Set one of these blocks depending on the wallet kind you chose above: - -# ClicheWallet -CLICHE_ENDPOINT=ws://127.0.0.1:12000 - -# SparkWallet -SPARK_URL=http://localhost:9737/rpc -SPARK_TOKEN=myaccesstoken - -# CLightningWallet -CLIGHTNING_RPC="/mnt/c-lightning/lightning-rpc" - -# LnbitsWallet -LNBITS_ENDPOINT=https://legend.lnbits.com -LNBITS_KEY=LNBITS_ADMIN_KEY - -# LndRestWallet -LND_REST_ENDPOINT=https://lnd.embassy:8080/ -LND_REST_CERT="/mnt/lnd/tls.cert" -LND_REST_MACAROON="/mnt/lnd/admin.macaroon" -# To use an AES-encrypted macaroon, set -# LND_REST_MACAROON_ENCRYPTED="eNcRyPtEdMaCaRoOn" - -# LNPayWallet -LNPAY_API_ENDPOINT=https://api.lnpay.co/v1/ -# Secret API Key under developers tab -LNPAY_API_KEY=LNPAY_API_KEY -# Wallet Admin in Wallet Access Keys -LNPAY_WALLET_KEY=LNPAY_ADMIN_KEY - -# LntxbotWallet -LNTXBOT_API_ENDPOINT=https://lntxbot.com/ -LNTXBOT_KEY=LNTXBOT_ADMIN_KEY - -# OpenNodeWallet -OPENNODE_API_ENDPOINT=https://api.opennode.com/ -OPENNODE_KEY=OPENNODE_ADMIN_KEY - -# FakeWallet -FAKE_WALLET_SECRET="ToTheMoon1" -LNBITS_DENOMINATION=sats - -# EclairWallet -ECLAIR_URL=http://127.0.0.1:8283 -ECLAIR_PASS=eclairpw - -# LnTipsWallet -# Enter /api in LightningTipBot to get your key -LNTIPS_API_KEY=LNTIPS_ADMIN_KEY -LNTIPS_API_ENDPOINT=https://ln.tips - -# Cashu Mint -# Use a long-enough random (!) private key. -# Once set, you cannot change this key as for now. -CASHU_PRIVATE_KEY="SuperSecretPrivateKey" diff --git a/Old/Makefile b/Old/Makefile deleted file mode 100644 index 05be490..0000000 --- a/Old/Makefile +++ /dev/null @@ -1,70 +0,0 @@ -PKG_ID := $(shell yq e ".id" manifest.yaml) -PKG_VERSION := $(shell yq e ".version" manifest.yaml) -TS_FILES := $(shell find ./ -name \*.ts) - -# delete the target of a rule if it has changed and its recipe exits with a nonzero exit status -.DELETE_ON_ERROR: - -all: submodule-update verify - -verify: $(PKG_ID).s9pk - @start-sdk verify s9pk $(PKG_ID).s9pk - @echo " Done!" - @echo " Filesize: $(shell du -h $(PKG_ID).s9pk) is ready" - -install: -ifeq (,$(wildcard ~/.embassy/config.yaml)) - @echo; echo "You must define \"host: http://server-name.local\" in ~/.embassy/config.yaml config file first"; echo -else - start-cli package install $(PKG_ID).s9pk -endif - -clean: - rm -rf docker-images - rm -f $(PKG_ID).s9pk - rm -f scripts/*.js - -submodule-update: - @if [ -z "$(shell git submodule status | egrep -v '^ '|awk '{print $$2}')" ]; then \ - echo "Submodules are up to date."; \ - else \ - echo "\nUpdating submodules...\n"; \ - git submodule update --init --progress; \ - fi - -scripts/embassy.js: $(TS_FILES) - deno run --allow-read --allow-write --allow-env --allow-net scripts/bundle.ts - -# for rebuilding just the arm image. will include docker-images/x86_64.tar into the s9pk if it exists -arm: - @rm -f docker-images/x86_64.tar - ARCH=aarch64 $(MAKE) - -# for rebuilding just the x86 image. will include docker-images/aarch64.tar into the s9pk if it exists -x86: - @rm -f docker-images/aarch64.tar - ARCH=x86_64 $(MAKE) - -docker-images/x86_64.tar: Dockerfile docker_entrypoint.sh -ifeq ($(ARCH),aarch64) -else - mkdir -p docker-images - docker buildx build --tag start9/$(PKG_ID)/main:$(PKG_VERSION) --platform=linux/amd64 --build-arg PLATFORM=amd64 -o type=docker,dest=docker-images/x86_64.tar . -endif - -docker-images/aarch64.tar: Dockerfile docker_entrypoint.sh -ifeq ($(ARCH),x86_64) -else - mkdir -p docker-images - docker buildx build --tag start9/$(PKG_ID)/main:$(PKG_VERSION) --platform=linux/arm64 --build-arg PLATFORM=arm64 -o type=docker,dest=docker-images/aarch64.tar . -endif - -$(PKG_ID).s9pk: manifest.yaml instructions.md LICENSE icon.png scripts/embassy.js docker-images/aarch64.tar docker-images/x86_64.tar -ifeq ($(ARCH),aarch64) - @echo "start-sdk: Preparing aarch64 package ..." -else ifeq ($(ARCH),x86_64) - @echo "start-sdk: Preparing x86_64 package ..." -else - @echo "start-sdk: Preparing Universal Package ..." -endif - @start-sdk pack diff --git a/Old/actions/reset-pass.sh b/Old/actions/reset-pass.sh deleted file mode 100644 index 345bf26..0000000 --- a/Old/actions/reset-pass.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash - -set -e - -SUPERUSER_ACCOUNT_ID=$(sqlite3 ./data/database.sqlite3 'select super_user from settings;') -ADMIN_PASS=$(cat /dev/urandom | base64 | head -c 24) -echo "$ADMIN_PASS" > /app/data/start9/admin_password.txt -CURRENT_DATETIME=$(date +%s) -PASS_HASH=$(python3 -c "import bcrypt; print(bcrypt.hashpw('$ADMIN_PASS'.encode('utf-8'), bcrypt.gensalt()).decode('utf-8'))") - -sqlite3 ./data/database.sqlite3 </dev/null; then - echo "LNBits Web interface is unreachable" >&2 - exit 1 - fi -fi diff --git a/Old/docker_entrypoint.sh b/Old/docker_entrypoint.sh deleted file mode 100755 index 9621180..0000000 --- a/Old/docker_entrypoint.sh +++ /dev/null @@ -1,216 +0,0 @@ -#!/bin/bash - -set -e - -# Handle termination signals -_term() { - echo "Received termination signal!" - kill -TERM "$lnbits_process" 2>/dev/null -} - -export LND_PATH="/mnt/lnd/admin.macaroon" -export CLN_PATH="/mnt/c-lightning/" -export TOR_ADDRESS=$(yq e '.tor-address' /app/data/start9/config.yaml) -export LAN_ADDRESS=$(yq e '.lan-address' /app/data/start9/config.yaml) -export LNBITS_BACKEND_WALLET_CLASS=$(yq e '.implementation' /app/data/start9/config.yaml) -export FILE="/app/data/database.sqlite3" -MACAROON_HEADER="" - -sed -i 's|LNBITS_BACKEND_WALLET_CLASS=.*|LNBITS_BACKEND_WALLET_CLASS='$LNBITS_BACKEND_WALLET_CLASS'|' /app/.env -sed -i 's|LNBITS_ALLOWED_FUNDING_SOURCES=.*|LNBITS_ALLOWED_FUNDING_SOURCES="'$LNBITS_BACKEND_WALLET_CLASS'"|' /app/.env - -if [ $LNBITS_BACKEND_WALLET_CLASS == "LndRestWallet" ]; then - MACAROON_HEADER="Grpc-Metadata-macaroon: $(xxd -ps -u -c 1000 /mnt/lnd/admin.macaroon)" - if ! [ -f $LND_PATH ]; then - echo "ERROR: Cannot find LND macaroon." - exit 1 - fi -elif [ $LNBITS_BACKEND_WALLET_CLASS == "CLightningWallet" ]; then - if ! [ -d $CLN_PATH ]; then - echo "ERROR: Cannot find Core Lightning path." - exit 1 - fi -fi - -configurator() { - while true; do { - # Properties Page showing password to be used for login - if [ -f $FILE ]; then - SUPERUSER_ACCOUNT=$(sqlite3 ./data/database.sqlite3 "select value from system_settings where id = 'super_user';" | sed 's/^"\(.*\)"$/\1/') - SUPERUSER_ACCOUNT_URL_PROP="https://$LAN_ADDRESS/wallet?usr=$SUPERUSER_ACCOUNT" - SUPERUSER_ACCOUNT_URL_TOR="http://$TOR_ADDRESS/wallet?usr=$SUPERUSER_ACCOUNT" - ADMIN_PASS=$(cat /app/data/start9/admin_password.txt) - PUBLIC_UI=$(sqlite3 ./data/database.sqlite3 "select value from system_settings where id = 'lnbits_public_node_ui';") - USER_ID_ONLY=$(sqlite3 ./data/database.sqlite3 "select value from system_settings where id = 'auth_allowed_methods';" | jq 'any(index("user-id-only"))') - - echo 'version: 2' >/app/data/start9/stats.yaml - echo 'data:' >>/app/data/start9/stats.yaml - - # Admin Credentials - echo " Superuser Username: " >>/app/data/start9/stats.yaml - echo ' type: string' >>/app/data/start9/stats.yaml - echo ' value: admin' >>/app/data/start9/stats.yaml - echo ' description: LNBits Superuser Account Username' >>/app/data/start9/stats.yaml - echo ' copyable: true' >>/app/data/start9/stats.yaml - echo ' masked: false' >>/app/data/start9/stats.yaml - echo ' qr: false' >>/app/data/start9/stats.yaml - echo " Superuser Default Password: " >>/app/data/start9/stats.yaml - echo ' type: string' >>/app/data/start9/stats.yaml - echo " value: $ADMIN_PASS" >>/app/data/start9/stats.yaml - echo ' description: LNBits Superuser Account Password' >>/app/data/start9/stats.yaml - echo ' copyable: true' >>/app/data/start9/stats.yaml - echo ' masked: true' >>/app/data/start9/stats.yaml - echo ' qr: false' >>/app/data/start9/stats.yaml - - # Node UI - if [ "$PUBLIC_UI" == "true" ]; then - echo " Public Node UI:" >>/app/data/start9/stats.yaml - echo ' type: string' >>/app/data/start9/stats.yaml - echo " value: \"http://$TOR_ADDRESS/node/public\"" >>/app/data/start9/stats.yaml - echo ' description: The URL of your LNbits Public Node UI. Share this URL with others so they can see basic information about your LN node.' >>/app/data/start9/stats.yaml - echo ' copyable: true' >>/app/data/start9/stats.yaml - echo ' masked: false' >>/app/data/start9/stats.yaml - echo ' qr: true' >>/app/data/start9/stats.yaml - fi - - if [ "$USER_ID_ONLY" == "true" ]; then - echo " Superuser Account: " >>/app/data/start9/stats.yaml - echo ' type: string' >>/app/data/start9/stats.yaml - echo " value: \"$SUPERUSER_ACCOUNT_URL_PROP\"" >>/app/data/start9/stats.yaml - echo ' description: LNBits Superuser Account' >>/app/data/start9/stats.yaml - echo ' copyable: true' >>/app/data/start9/stats.yaml - echo ' masked: true' >>/app/data/start9/stats.yaml - echo ' qr: true' >>/app/data/start9/stats.yaml - echo " (Tor) Superuser Account: " >>/app/data/start9/stats.yaml - echo ' type: string' >>/app/data/start9/stats.yaml - echo " value: \"$SUPERUSER_ACCOUNT_URL_TOR\"" >>/app/data/start9/stats.yaml - echo ' description: LNBits Superuser Account' >>/app/data/start9/stats.yaml - echo ' copyable: true' >>/app/data/start9/stats.yaml - echo ' masked: true' >>/app/data/start9/stats.yaml - echo ' qr: true' >>/app/data/start9/stats.yaml - - sqlite3 ./data/database.sqlite3 'select id from accounts;' >account.res - mapfile -t LNBITS_ACCOUNTS wallet.res - mapfile -t LNBITS_WALLETS >/app/data/start9/stats.yaml - echo ' type: string' >>/app/data/start9/stats.yaml - echo " value: \"$ACCOUNT_URL_PROP\"" >>/app/data/start9/stats.yaml - echo ' description: LNBits Account' >>/app/data/start9/stats.yaml - echo ' copyable: true' >>/app/data/start9/stats.yaml - echo ' masked: true' >>/app/data/start9/stats.yaml - echo ' qr: true' >>/app/data/start9/stats.yaml - echo " (Tor) LNBits Account $USER_ID - Wallet $LNBITS_WALLET_NAME: " >>/app/data/start9/stats.yaml - echo ' type: string' >>/app/data/start9/stats.yaml - echo " value: \"$ACCOUNT_URL_TOR\"" >>/app/data/start9/stats.yaml - echo ' description: LNBits Account' >>/app/data/start9/stats.yaml - echo ' copyable: true' >>/app/data/start9/stats.yaml - echo ' masked: true' >>/app/data/start9/stats.yaml - echo ' qr: true' >>/app/data/start9/stats.yaml - fi - }; done - }; done - fi - else - echo 'No existing database found.' - fi - sleep 10 - }; done -} - -printf "\n\n [i] Starting LNBits...\n\n" - -if [ "$CONFIG_LN_IMPLEMENTATION" = "LndRestWallet" ]; then - until curl --silent --fail --cacert /mnt/lnd/tls.cert --header "$MACAROON_HEADER" https://lnd.embassy:8080/v1/getinfo &>/dev/null - do - echo "LND Server is unreachable. Are you sure the LND service is running?" - sleep 5 - done -fi - -poetry run lnbits --port $LNBITS_PORT --host $LNBITS_HOST & -lnbits_process=$! - -until ( - sqlite3 ./data/database.sqlite3 'PRAGMA table_info(system_settings);' 2>/dev/null | grep -q "value" -); do - echo "Waiting for migrations to complete..." - sleep 10 -done - -if [ -f $FILE ]; then - echo "Checking if underlying LN implementation has changed..." - EXISTING_CONFIG_LN_IMPLEMENTATION=$(sqlite3 ./data/database.sqlite3 "select value from system_settings where id = 'lnbits_backend_wallet_class';") - echo "LNBITS_BACKEND_WALLET_CLASS: $LNBITS_BACKEND_WALLET_CLASS" - echo "EXISTING_CONFIG_LN_IMPLEMENTATION: $EXISTING_CONFIG_LN_IMPLEMENTATION" - - if [ "\"$LNBITS_BACKEND_WALLET_CLASS\"" != "$EXISTING_CONFIG_LN_IMPLEMENTATION" ]; then - echo "Configured LN implementation is not the same as the existing LN implementation" - echo "Deleting previous LN implementation data" - rm $FILE - rm /app/data/start9/stats.yaml - else - echo "Looking for existing accounts and wallets..." - sqlite3 ./data/database.sqlite3 'select id from accounts;' >>account.res - mapfile -t LNBITS_ACCOUNTS /app/data/start9/admin_password.txt - PASS_HASH=$(python3 -c "import bcrypt; print(bcrypt.hashpw('$ADMIN_PASS'.encode('utf-8'), bcrypt.gensalt()).decode('utf-8'))") - NEW_SUPERUSER_EXTRA=$(echo $OLD_SUPERUSER_EXTRA | jq -c '.provider = "lnbits"') - sqlite3 ./data/database.sqlite3 </dev/null -fi - -configurator & - -# Set up a signal trap and wait for processes to finish -trap _term TERM -wait $lnbits_process diff --git a/Old/manifest.yaml b/Old/manifest.yaml deleted file mode 100644 index 74da358..0000000 --- a/Old/manifest.yaml +++ /dev/null @@ -1,143 +0,0 @@ -id: lnbits -title: "LNBits" -version: 1.1.0 -release-notes: | - * Update to v1.1.0 [release notes](https://github.com/lnbits/lnbits/releases/tag/v1.1.0) -license: MIT -wrapper-repo: "https://github.com/Start9Labs/lnbits-startos" -upstream-repo: "https://github.com/lnbits/lnbits" -support-site: "https://github.com/lnbits/lnbits/issues" -marketing-site: "https://lnbits.com/" -donation-url: "https://legend.lnbits.com/paywall/GAqKguK5S8f6w5VNjS9DfK" -build: ["make"] -description: - short: Free and open-source lightning-network wallet/accounts system. - long: | - A very simple Python server that sits on top of any funding source, and can be used as an accounts system, extendable platform, development stack, fallback wallet or even instant wallet for LN demonstrations -assets: - license: LICENSE - icon: icon.png - instructions: instructions.md -main: - type: docker - image: main - entrypoint: docker_entrypoint.sh - args: [] - mounts: - main: /app/data - lnd: /mnt/lnd - c-lightning: "/mnt/c-lightning" -health-checks: - main: - name: Web Interface - success-message: LNBits is ready to visit in a web browser - type: docker - image: main - entrypoint: "check-web.sh" - args: [] - inject: true - system: false - io-format: json -config: - get: - type: script - set: - type: script -properties: - type: script -volumes: - main: - type: data - lnd: - type: pointer - package-id: lnd - volume-id: main - path: "/public" - readonly: true - c-lightning: - type: pointer - package-id: c-lightning - volume-id: main - path: /shared - readonly: true -interfaces: - main: - name: LNBits Web Interface - description: Specifies the interface to listen on for HTTP connections. - tor-config: - port-mapping: - 80: "5000" - lan-config: - 443: - ssl: true - internal: 5000 - ui: true - protocols: - - tcp - - http -dependencies: - lnd: - version: ">=0.14.3 <0.20.0" - description: Used to communicate with the Lightning Network. - requirement: - type: "opt-in" - how: Use the LND instance by default - config: ~ - c-lightning: - version: ">=0.10.1 <26.0.0" - description: Used to communicate with the Lightning Network. - requirement: - type: "opt-in" - how: Can opt to use the CLN instance instead of LND - config: ~ -backup: - create: - type: docker - image: compat - system: true - entrypoint: compat - args: - - duplicity - - create - - /mnt/backup - - /app/data/data - mounts: - BACKUP: "/mnt/backup" - main: "/app/data/data" - restore: - type: docker - image: compat - system: true - entrypoint: compat - args: - - duplicity - - restore - - /mnt/backup - - /app/data/data - mounts: - BACKUP: "/mnt/backup" - main: "/app/data/data" -migrations: - from: - "*": - type: script - args: ["from"] - to: - "*": - type: script - args: ["to"] -actions: - reset-pass: - name: "Reset Superuser Password" - description: "Resets a forgotten or compromised Superuser password to a freshly generated random password available in Properties." - warning: ~ - allowed-statuses: - - running - implementation: - type: docker - image: main - system: false - entrypoint: reset-pass.sh - inject: true - args: [] - io-format: json diff --git a/Old/scripts/bundle.ts b/Old/scripts/bundle.ts deleted file mode 100644 index a63ef3e..0000000 --- a/Old/scripts/bundle.ts +++ /dev/null @@ -1,6 +0,0 @@ -// scripts/bundle.ts -import { bundle } from "https://deno.land/x/emit@0.40.0/mod.ts"; - -const result = await bundle("scripts/embassy.ts"); - -await Deno.writeTextFile("scripts/embassy.js", result.code); \ No newline at end of file diff --git a/Old/scripts/deps.ts b/Old/scripts/deps.ts deleted file mode 100644 index 12d1eb5..0000000 --- a/Old/scripts/deps.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "https://deno.land/x/embassyd_sdk@v0.3.3.0.4/mod.ts"; diff --git a/Old/scripts/embassy.js b/Old/scripts/embassy.js deleted file mode 100644 index d33355d..0000000 --- a/Old/scripts/embassy.js +++ /dev/null @@ -1,4014 +0,0 @@ -function saferStringify(x) { - try { - return JSON.stringify(x); - } catch (e) { - return "" + x; - } -} -class AnyParser { - description; - constructor(description = { - name: "Any", - children: [], - extras: [] - }){ - this.description = description; - } - parse(a, onParse) { - return onParse.parsed(a); - } -} -class ArrayParser { - description; - constructor(description = { - name: "Array", - children: [], - extras: [] - }){ - this.description = description; - } - parse(a, onParse) { - if (Array.isArray(a)) return onParse.parsed(a); - return onParse.invalid({ - value: a, - keys: [], - parser: this - }); - } -} -class BoolParser { - description; - constructor(description = { - name: "Boolean", - children: [], - extras: [] - }){ - this.description = description; - } - parse(a, onParse) { - if (a === true || a === false) return onParse.parsed(a); - return onParse.invalid({ - value: a, - keys: [], - parser: this - }); - } -} -const isObject = (x)=>typeof x === "object" && x != null; -const isFunctionTest = (x)=>typeof x === "function"; -const isNumber = (x)=>typeof x === "number"; -const isString = (x)=>typeof x === "string"; -const booleanOnParse = { - parsed (_) { - return true; - }, - invalid (_) { - return false; - } -}; -class FunctionParser { - description; - constructor(description = { - name: "Function", - children: [], - extras: [] - }){ - this.description = description; - } - parse(a, onParse) { - if (isFunctionTest(a)) return onParse.parsed(a); - return onParse.invalid({ - value: a, - keys: [], - parser: this - }); - } -} -class NilParser { - description; - constructor(description = { - name: "Null", - children: [], - extras: [] - }){ - this.description = description; - } - parse(a, onParse) { - if (a === null || a === undefined) return onParse.parsed(a); - return onParse.invalid({ - value: a, - keys: [], - parser: this - }); - } -} -class ObjectParser { - description; - constructor(description = { - name: "Object", - children: [], - extras: [] - }){ - this.description = description; - } - parse(a, onParse) { - if (isObject(a)) return onParse.parsed(a); - return onParse.invalid({ - value: a, - keys: [], - parser: this - }); - } -} -class StringParser { - description; - constructor(description = { - name: "String", - children: [], - extras: [] - }){ - this.description = description; - } - parse(a, onParse) { - if (isString(a)) return onParse.parsed(a); - return onParse.invalid({ - value: a, - keys: [], - parser: this - }); - } -} -class UnknownParser { - description; - constructor(description = { - name: "Unknown", - children: [], - extras: [] - }){ - this.description = description; - } - parse(a, onParse) { - return onParse.parsed(a); - } -} -class ConcatParsers { - parent; - otherParser; - description; - constructor(parent, otherParser, description = { - name: "Concat", - children: [ - parent, - otherParser - ], - extras: [] - }){ - this.parent = parent; - this.otherParser = otherParser; - this.description = description; - } - static of(parent, otherParser) { - if (parent.unwrappedParser().description.name === "Any") { - return otherParser; - } - if (otherParser.unwrappedParser().description.name === "Any") { - return parent; - } - return new ConcatParsers(parent, otherParser); - } - parse(a, onParse) { - const parent = this.parent.enumParsed(a); - if ("error" in parent) { - return onParse.invalid(parent.error); - } - const other = this.otherParser.enumParsed(parent.value); - if ("error" in other) { - return onParse.invalid(other.error); - } - return onParse.parsed(other.value); - } -} -class DefaultParser { - parent; - defaultValue; - description; - constructor(parent, defaultValue, description = { - name: "Default", - children: [ - parent - ], - extras: [ - defaultValue - ] - }){ - this.parent = parent; - this.defaultValue = defaultValue; - this.description = description; - } - parse(a, onParse) { - const parser = this; - const defaultValue = this.defaultValue; - if (a == null) { - return onParse.parsed(defaultValue); - } - const parentCheck = this.parent.enumParsed(a); - if ("error" in parentCheck) { - parentCheck.error.parser = parser; - return onParse.invalid(parentCheck.error); - } - return onParse.parsed(parentCheck.value); - } -} -class GuardParser { - checkIsA; - typeName; - description; - constructor(checkIsA, typeName, description = { - name: "Guard", - children: [], - extras: [ - typeName - ] - }){ - this.checkIsA = checkIsA; - this.typeName = typeName; - this.description = description; - } - parse(a, onParse) { - if (this.checkIsA(a)) { - return onParse.parsed(a); - } - return onParse.invalid({ - value: a, - keys: [], - parser: this - }); - } -} -class MappedAParser { - parent; - map; - mappingName; - description; - constructor(parent, map, mappingName = map.name, description = { - name: "Mapped", - children: [ - parent - ], - extras: [ - mappingName - ] - }){ - this.parent = parent; - this.map = map; - this.mappingName = mappingName; - this.description = description; - } - parse(a, onParse) { - const map = this.map; - const result = this.parent.enumParsed(a); - if ("error" in result) { - return onParse.invalid(result.error); - } - return onParse.parsed(map(result.value)); - } -} -class MaybeParser { - parent; - description; - constructor(parent, description = { - name: "Maybe", - children: [ - parent - ], - extras: [] - }){ - this.parent = parent; - this.description = description; - } - parse(a, onParse) { - if (a == null) { - return onParse.parsed(null); - } - const parser = this; - const parentState = this.parent.enumParsed(a); - if ("error" in parentState) { - const { error } = parentState; - error.parser = parser; - return onParse.invalid(error); - } - return onParse.parsed(parentState.value); - } -} -class OrParsers { - parent; - otherParser; - description; - constructor(parent, otherParser, description = { - name: "Or", - children: [ - parent, - otherParser - ], - extras: [] - }){ - this.parent = parent; - this.otherParser = otherParser; - this.description = description; - } - parse(a, onParse) { - const parser = this; - const parent = this.parent.enumParsed(a); - if ("value" in parent) { - return onParse.parsed(parent.value); - } - const other = this.otherParser.enumParsed(a); - if ("error" in other) { - const { error } = other; - error.parser = parser; - return onParse.invalid(error); - } - return onParse.parsed(other.value); - } -} -class NumberParser { - description; - constructor(description = { - name: "Number", - children: [], - extras: [] - }){ - this.description = description; - } - parse(a, onParse) { - if (isNumber(a)) return onParse.parsed(a); - return onParse.invalid({ - value: a, - keys: [], - parser: this - }); - } -} -function unwrapParser(a) { - if (a instanceof Parser) return unwrapParser(a.parser); - return a; -} -const enumParsed = { - parsed (value) { - return { - value - }; - }, - invalid (error) { - return { - error - }; - } -}; -class Parser { - parser; - description; - _TYPE; - constructor(parser, description = { - name: "Wrapper", - children: [ - parser - ], - extras: [] - }){ - this.parser = parser; - this.description = description; - this._TYPE = null; - this.test = (value)=>{ - return this.parse(value, booleanOnParse); - }; - } - parse(a, onParse) { - return this.parser.parse(a, onParse); - } - static isA(checkIsA, name) { - return new Parser(new GuardParser(checkIsA, name)); - } - static validatorErrorAsString = (error)=>{ - const { parser, value, keys } = error; - const keysString = !keys.length ? "" : keys.map((x)=>`[${x}]`).reverse().join(""); - return `${keysString}${Parser.parserAsString(parser)}(${saferStringify(value)})`; - }; - static parserAsString(parserComingIn) { - const parser = unwrapParser(parserComingIn); - const { description: { name, extras, children } } = parser; - if (parser instanceof ShapeParser) { - return `${name}<{${parser.description.children.map((subParser, i)=>`${String(parser.description.extras[i]) || "?"}:${Parser.parserAsString(subParser)}`).join(",")}}>`; - } - if (parser instanceof OrParsers) { - const parent = unwrapParser(parser.parent); - const parentString = Parser.parserAsString(parent); - if (parent instanceof OrParsers) return parentString; - return `${name}<${parentString},...>`; - } - if (parser instanceof GuardParser) { - return String(extras[0] || name); - } - if (parser instanceof StringParser || parser instanceof ObjectParser || parser instanceof NumberParser || parser instanceof BoolParser || parser instanceof AnyParser) { - return name.toLowerCase(); - } - if (parser instanceof FunctionParser) { - return name; - } - if (parser instanceof NilParser) { - return "null"; - } - if (parser instanceof ArrayParser) { - return "Array"; - } - const specifiers = [ - ...extras.map(saferStringify), - ...children.map(Parser.parserAsString) - ]; - const specifiersString = `<${specifiers.join(",")}>`; - !children.length ? "" : `<>`; - return `${name}${specifiersString}`; - } - unsafeCast(value) { - const state = this.enumParsed(value); - if ("value" in state) return state.value; - const { error } = state; - throw new TypeError(`Failed type: ${Parser.validatorErrorAsString(error)} given input ${saferStringify(value)}`); - } - castPromise(value) { - const state = this.enumParsed(value); - if ("value" in state) return Promise.resolve(state.value); - const { error } = state; - return Promise.reject(new TypeError(`Failed type: ${Parser.validatorErrorAsString(error)} given input ${saferStringify(value)}`)); - } - errorMessage(input) { - const parsed = this.parse(input, enumParsed); - if ("value" in parsed) return; - return Parser.validatorErrorAsString(parsed.error); - } - map(fn, mappingName) { - return new Parser(new MappedAParser(this, fn, mappingName)); - } - concat(otherParser) { - return new Parser(ConcatParsers.of(this, new Parser(otherParser))); - } - orParser(otherParser) { - return new Parser(new OrParsers(this, new Parser(otherParser))); - } - test; - optional(name) { - return new Parser(new MaybeParser(this)); - } - defaultTo(defaultValue) { - return new Parser(new DefaultParser(new Parser(new MaybeParser(this)), defaultValue)); - } - validate(isValid, otherName) { - return new Parser(ConcatParsers.of(this, new Parser(new GuardParser(isValid, otherName)))); - } - refine(refinementTest, otherName = refinementTest.name) { - return new Parser(ConcatParsers.of(this, new Parser(new GuardParser(refinementTest, otherName)))); - } - name(nameString) { - return parserName(nameString, this); - } - enumParsed(value) { - return this.parse(value, enumParsed); - } - unwrappedParser() { - let answer = this; - while(true){ - const next = answer.parser; - if (next instanceof Parser) { - answer = next; - } else { - return next; - } - } - } -} -function guard(test, testName) { - return Parser.isA(test, testName || test.name); -} -const any = new Parser(new AnyParser()); -class ArrayOfParser { - parser; - description; - constructor(parser, description = { - name: "ArrayOf", - children: [ - parser - ], - extras: [] - }){ - this.parser = parser; - this.description = description; - } - parse(a, onParse) { - if (!Array.isArray(a)) { - return onParse.invalid({ - value: a, - keys: [], - parser: this - }); - } - const values = [ - ...a - ]; - for(let index = 0; index < values.length; index++){ - const result = this.parser.enumParsed(values[index]); - if ("error" in result) { - result.error.keys.push("" + index); - return onParse.invalid(result.error); - } else { - values[index] = result.value; - } - } - return onParse.parsed(values); - } -} -function arrayOf(validator) { - return new Parser(new ArrayOfParser(validator)); -} -const unknown = new Parser(new UnknownParser()); -const number = new Parser(new NumberParser()); -const isNill = new Parser(new NilParser()); -const natural = number.refine((x)=>x >= 0 && x === Math.floor(x)); -const isFunction = new Parser(new FunctionParser()); -const __boolean = new Parser(new BoolParser()); -class DeferredParser { - description; - parser; - static create() { - return new DeferredParser(); - } - constructor(description = { - name: "Deferred", - children: [], - extras: [] - }){ - this.description = description; - } - setParser(parser) { - this.parser = new Parser(parser); - return this; - } - parse(a, onParse) { - if (!this.parser) { - return onParse.invalid({ - value: "Not Set Up", - keys: [], - parser: this - }); - } - return this.parser.parse(a, onParse); - } -} -function deferred() { - const deferred = DeferredParser.create(); - function setParser(parser) { - deferred.setParser(parser); - } - return [ - new Parser(deferred), - setParser - ]; -} -const object = new Parser(new ObjectParser()); -class DictionaryParser { - parsers; - description; - constructor(parsers, description = { - name: "Dictionary", - children: parsers.reduce((acc, [k, v])=>{ - acc.push(k, v); - return acc; - }, []), - extras: [] - }){ - this.parsers = parsers; - this.description = description; - } - parse(a, onParse) { - const { parsers } = this; - const parser = this; - const answer = { - ...a - }; - outer: for(const key in a){ - let parseError = []; - for (const [keyParser, valueParser] of parsers){ - const enumState = keyParser.enumParsed(key); - if ("error" in enumState) { - const { error } = enumState; - error.parser = parser; - error.keys.push("" + key); - parseError.push(error); - continue; - } - const newKey = enumState.value; - const valueState = valueParser.enumParsed(a[key]); - if ("error" in valueState) { - const { error } = valueState; - error.keys.push("" + newKey); - parseError.unshift(error); - continue; - } - delete answer[key]; - answer[newKey] = valueState.value; - break outer; - } - const error = parseError[0]; - if (!!error) { - return onParse.invalid(error); - } - } - return onParse.parsed(answer); - } -} -const dictionary = (...parsers)=>{ - return object.concat(new DictionaryParser([ - ...parsers - ])); -}; -function every(...parsers) { - const filteredParsers = parsers.filter((x)=>x !== any); - if (filteredParsers.length <= 0) { - return any; - } - const first = filteredParsers.splice(0, 1)[0]; - return filteredParsers.reduce((left, right)=>{ - return left.concat(right); - }, first); -} -const isArray = new Parser(new ArrayParser()); -const string = new Parser(new StringParser()); -const instanceOf = (classCreator)=>guard((x)=>x instanceof classCreator, `is${classCreator.name}`); -class LiteralsParser { - values; - description; - constructor(values, description = { - name: "Literal", - children: [], - extras: values - }){ - this.values = values; - this.description = description; - } - parse(a, onParse) { - if (this.values.indexOf(a) >= 0) { - return onParse.parsed(a); - } - return onParse.invalid({ - value: a, - keys: [], - parser: this - }); - } -} -function literal(isEqualToValue) { - return new Parser(new LiteralsParser([ - isEqualToValue - ])); -} -function literals(firstValue, ...restValues) { - return new Parser(new LiteralsParser([ - firstValue, - ...restValues - ])); -} -class ShapeParser { - parserMap; - isPartial; - parserKeys; - description; - constructor(parserMap, isPartial, parserKeys = Object.keys(parserMap), description = { - name: isPartial ? "Partial" : "Shape", - children: parserKeys.map((key)=>parserMap[key]), - extras: parserKeys - }){ - this.parserMap = parserMap; - this.isPartial = isPartial; - this.parserKeys = parserKeys; - this.description = description; - } - parse(a, onParse) { - const parser = this; - if (!object.test(a)) { - return onParse.invalid({ - value: a, - keys: [], - parser - }); - } - const { parserMap, isPartial } = this; - const value = { - ...a - }; - if (Array.isArray(a)) { - value.length = a.length; - } - for(const key in parserMap){ - if (key in value) { - const parser = parserMap[key]; - const state = parser.enumParsed(a[key]); - if ("error" in state) { - const { error } = state; - error.keys.push(saferStringify(key)); - return onParse.invalid(error); - } - const smallValue = state.value; - value[key] = smallValue; - } else if (!isPartial) { - return onParse.invalid({ - value: "missingProperty", - parser, - keys: [ - saferStringify(key) - ] - }); - } - } - return onParse.parsed(value); - } -} -const isPartial = (testShape)=>{ - return new Parser(new ShapeParser(testShape, true)); -}; -const partial = isPartial; -class RecursiveParser { - recursive; - description; - parser; - static create(fn) { - const parser = new RecursiveParser(fn); - parser.parser = fn(new Parser(parser)); - return parser; - } - constructor(recursive, description = { - name: "Recursive", - children: [], - extras: [ - recursive - ] - }){ - this.recursive = recursive; - this.description = description; - } - parse(a, onParse) { - if (!this.parser) { - return onParse.invalid({ - value: "Recursive Invalid State", - keys: [], - parser: this - }); - } - return this.parser.parse(a, onParse); - } -} -function recursive(fn) { - fn(any); - const created = RecursiveParser.create(fn); - return new Parser(created); -} -const regex = (tester)=>string.refine(function(x) { - return tester.test(x); - }, tester.toString()); -const isShape = (testShape)=>{ - return new Parser(new ShapeParser(testShape, false)); -}; -function shape(testShape, optionals, optionalAndDefaults) { - if (optionals) { - const defaults = optionalAndDefaults || {}; - const entries = Object.entries(testShape); - const optionalSet = new Set(Array.from(optionals)); - return every(partial(Object.fromEntries(entries.filter(([key, _])=>optionalSet.has(key)).map(([key, parser])=>[ - key, - parser.optional() - ]))), isShape(Object.fromEntries(entries.filter(([key, _])=>!optionalSet.has(key))))).map((ret)=>{ - for (const key of optionalSet){ - const keyAny = key; - if (!(keyAny in ret) && keyAny in defaults) { - ret[keyAny] = defaults[keyAny]; - } - } - return ret; - }); - } - return isShape(testShape); -} -function some(...parsers) { - if (parsers.length <= 0) { - return any; - } - const first = parsers.splice(0, 1)[0]; - return parsers.reduce((left, right)=>left.orParser(right), first); -} -class TupleParser { - parsers; - lengthMatcher; - description; - constructor(parsers, lengthMatcher = literal(parsers.length), description = { - name: "Tuple", - children: parsers, - extras: [] - }){ - this.parsers = parsers; - this.lengthMatcher = lengthMatcher; - this.description = description; - } - parse(input, onParse) { - const tupleError = isArray.enumParsed(input); - if ("error" in tupleError) return onParse.invalid(tupleError.error); - const values = input; - const stateCheck = this.lengthMatcher.enumParsed(values.length); - if ("error" in stateCheck) { - stateCheck.error.keys.push(saferStringify("length")); - return onParse.invalid(stateCheck.error); - } - const answer = new Array(this.parsers.length); - for(const key in this.parsers){ - const parser = this.parsers[key]; - const value = values[key]; - const result = parser.enumParsed(value); - if ("error" in result) { - const { error } = result; - error.keys.push(saferStringify(key)); - return onParse.invalid(error); - } - answer[key] = result.value; - } - return onParse.parsed(answer); - } -} -function tuple(...parsers) { - return new Parser(new TupleParser(parsers)); -} -class NamedParser { - parent; - name; - description; - constructor(parent, name, description = { - name: "Named", - children: [ - parent - ], - extras: [ - name - ] - }){ - this.parent = parent; - this.name = name; - this.description = description; - } - parse(a, onParse) { - const parser = this; - const parent = this.parent.enumParsed(a); - if ("error" in parent) { - const { error } = parent; - error.parser = parser; - return onParse.invalid(error); - } - return onParse.parsed(parent.value); - } -} -function parserName(name, parent) { - return new Parser(new NamedParser(parent, name)); -} -class Matched { - value; - constructor(value){ - this.value = value; - } - when(..._args) { - return this; - } - defaultTo(_defaultValue) { - return this.value; - } - defaultToLazy(_getValue) { - return this.value; - } - unwrap() { - return this.value; - } -} -class MatchMore { - a; - constructor(a){ - this.a = a; - } - when(...args) { - const [outcome, ...matchers] = args.reverse(); - const me = this; - const parser = matches.some(...matchers.map((matcher)=>matcher instanceof Parser ? matcher : literal(matcher))); - const result = parser.enumParsed(this.a); - if ("error" in result) { - return me; - } - const { value } = result; - if (outcome instanceof Function) { - return new Matched(outcome(value)); - } - return new Matched(outcome); - } - defaultTo(value) { - return value; - } - defaultToLazy(getValue) { - return getValue(); - } - unwrap() { - throw new Error("Expecting that value is matched"); - } -} -const matches = Object.assign(function matchesFn(value) { - return new MatchMore(value); -}, { - array: isArray, - arrayOf, - some, - tuple, - regex, - number, - natural, - isFunction, - object, - string, - shape, - partial, - literal, - every, - guard, - unknown, - any, - boolean: __boolean, - dictionary, - literals, - nill: isNill, - instanceOf, - Parse: Parser, - parserName, - recursive, - deferred -}); -const mod = { - AnyParser: AnyParser, - ArrayParser: ArrayParser, - BoolParser: BoolParser, - FunctionParser: FunctionParser, - GuardParser: GuardParser, - NilParser: NilParser, - NumberParser: NumberParser, - ObjectParser: ObjectParser, - OrParsers: OrParsers, - ShapeParser: ShapeParser, - StringParser: StringParser, - saferStringify: saferStringify, - NamedParser: NamedParser, - ArrayOfParser: ArrayOfParser, - LiteralsParser: LiteralsParser, - ConcatParsers: ConcatParsers, - MappedAParser: MappedAParser, - default: matches, - Validator: Parser, - matches, - allOf: every, - any, - anyOf: some, - array: isArray, - arrayOf, - boolean: __boolean, - deferred, - dictionary, - every, - guard, - instanceOf, - isFunction, - literal, - literals, - natural, - nill: isNill, - number, - object, - oneOf: some, - Parse: Parser, - Parser, - parserName, - partial, - recursive, - regex, - shape, - some, - string, - tuple, - unknown -}; -class YAMLError extends Error { - mark; - constructor(message = "(unknown reason)", mark = ""){ - super(`${message} ${mark}`); - this.mark = mark; - this.name = this.constructor.name; - } - toString(_compact) { - return `${this.name}: ${this.message} ${this.mark}`; - } -} -function isBoolean(value) { - return typeof value === "boolean" || value instanceof Boolean; -} -function isObject1(value) { - return value !== null && typeof value === "object"; -} -function repeat(str, count) { - let result = ""; - for(let cycle = 0; cycle < count; cycle++){ - result += str; - } - return result; -} -function isNegativeZero(i) { - return i === 0 && Number.NEGATIVE_INFINITY === 1 / i; -} -class Mark { - name; - buffer; - position; - line; - column; - constructor(name, buffer, position, line, column){ - this.name = name; - this.buffer = buffer; - this.position = position; - this.line = line; - this.column = column; - } - getSnippet(indent = 4, maxLength = 75) { - if (!this.buffer) return null; - let head = ""; - let start = this.position; - while(start > 0 && "\x00\r\n\x85\u2028\u2029".indexOf(this.buffer.charAt(start - 1)) === -1){ - start -= 1; - if (this.position - start > maxLength / 2 - 1) { - head = " ... "; - start += 5; - break; - } - } - let tail = ""; - let end = this.position; - while(end < this.buffer.length && "\x00\r\n\x85\u2028\u2029".indexOf(this.buffer.charAt(end)) === -1){ - end += 1; - if (end - this.position > maxLength / 2 - 1) { - tail = " ... "; - end -= 5; - break; - } - } - const snippet = this.buffer.slice(start, end); - return `${repeat(" ", indent)}${head}${snippet}${tail}\n${repeat(" ", indent + this.position - start + head.length)}^`; - } - toString(compact) { - let snippet, where = ""; - if (this.name) { - where += `in "${this.name}" `; - } - where += `at line ${this.line + 1}, column ${this.column + 1}`; - if (!compact) { - snippet = this.getSnippet(); - if (snippet) { - where += `:\n${snippet}`; - } - } - return where; - } -} -function compileList(schema, name, result) { - const exclude = []; - for (const includedSchema of schema.include){ - result = compileList(includedSchema, name, result); - } - for (const currentType of schema[name]){ - for(let previousIndex = 0; previousIndex < result.length; previousIndex++){ - const previousType = result[previousIndex]; - if (previousType.tag === currentType.tag && previousType.kind === currentType.kind) { - exclude.push(previousIndex); - } - } - result.push(currentType); - } - return result.filter((_type, index)=>!exclude.includes(index)); -} -function compileMap(...typesList) { - const result = { - fallback: {}, - mapping: {}, - scalar: {}, - sequence: {} - }; - for (const types of typesList){ - for (const type of types){ - if (type.kind !== null) { - result[type.kind][type.tag] = result["fallback"][type.tag] = type; - } - } - } - return result; -} -class Schema { - static SCHEMA_DEFAULT; - implicit; - explicit; - include; - compiledImplicit; - compiledExplicit; - compiledTypeMap; - constructor(definition){ - this.explicit = definition.explicit || []; - this.implicit = definition.implicit || []; - this.include = definition.include || []; - for (const type of this.implicit){ - if (type.loadKind && type.loadKind !== "scalar") { - throw new YAMLError("There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported."); - } - } - this.compiledImplicit = compileList(this, "implicit", []); - this.compiledExplicit = compileList(this, "explicit", []); - this.compiledTypeMap = compileMap(this.compiledImplicit, this.compiledExplicit); - } - extend(definition) { - return new Schema({ - implicit: [ - ...new Set([ - ...this.implicit, - ...definition?.implicit ?? [] - ]) - ], - explicit: [ - ...new Set([ - ...this.explicit, - ...definition?.explicit ?? [] - ]) - ], - include: [ - ...new Set([ - ...this.include, - ...definition?.include ?? [] - ]) - ] - }); - } - static create() {} -} -const DEFAULT_RESOLVE = ()=>true; -const DEFAULT_CONSTRUCT = (data)=>data; -function checkTagFormat(tag) { - return tag; -} -class Type { - tag; - kind = null; - instanceOf; - predicate; - represent; - defaultStyle; - styleAliases; - loadKind; - constructor(tag, options){ - this.tag = checkTagFormat(tag); - if (options) { - this.kind = options.kind; - this.resolve = options.resolve || DEFAULT_RESOLVE; - this.construct = options.construct || DEFAULT_CONSTRUCT; - this.instanceOf = options.instanceOf; - this.predicate = options.predicate; - this.represent = options.represent; - this.defaultStyle = options.defaultStyle; - this.styleAliases = options.styleAliases; - } - } - resolve = ()=>true; - construct = (data)=>data; -} -class DenoStdInternalError extends Error { - constructor(message){ - super(message); - this.name = "DenoStdInternalError"; - } -} -function assert(expr, msg = "") { - if (!expr) { - throw new DenoStdInternalError(msg); - } -} -function copy(src, dst, off = 0) { - off = Math.max(0, Math.min(off, dst.byteLength)); - const dstBytesAvailable = dst.byteLength - off; - if (src.byteLength > dstBytesAvailable) { - src = src.subarray(0, dstBytesAvailable); - } - dst.set(src, off); - return src.byteLength; -} -const MIN_READ = 32 * 1024; -const MAX_SIZE = 2 ** 32 - 2; -class Buffer { - #buf; - #off = 0; - constructor(ab){ - this.#buf = ab === undefined ? new Uint8Array(0) : new Uint8Array(ab); - } - bytes(options = { - copy: true - }) { - if (options.copy === false) return this.#buf.subarray(this.#off); - return this.#buf.slice(this.#off); - } - empty() { - return this.#buf.byteLength <= this.#off; - } - get length() { - return this.#buf.byteLength - this.#off; - } - get capacity() { - return this.#buf.buffer.byteLength; - } - truncate(n) { - if (n === 0) { - this.reset(); - return; - } - if (n < 0 || n > this.length) { - throw Error("bytes.Buffer: truncation out of range"); - } - this.#reslice(this.#off + n); - } - reset() { - this.#reslice(0); - this.#off = 0; - } - #tryGrowByReslice(n) { - const l = this.#buf.byteLength; - if (n <= this.capacity - l) { - this.#reslice(l + n); - return l; - } - return -1; - } - #reslice(len) { - assert(len <= this.#buf.buffer.byteLength); - this.#buf = new Uint8Array(this.#buf.buffer, 0, len); - } - readSync(p) { - if (this.empty()) { - this.reset(); - if (p.byteLength === 0) { - return 0; - } - return null; - } - const nread = copy(this.#buf.subarray(this.#off), p); - this.#off += nread; - return nread; - } - read(p) { - const rr = this.readSync(p); - return Promise.resolve(rr); - } - writeSync(p) { - const m = this.#grow(p.byteLength); - return copy(p, this.#buf, m); - } - write(p) { - const n = this.writeSync(p); - return Promise.resolve(n); - } - #grow(n) { - const m = this.length; - if (m === 0 && this.#off !== 0) { - this.reset(); - } - const i = this.#tryGrowByReslice(n); - if (i >= 0) { - return i; - } - const c = this.capacity; - if (n <= Math.floor(c / 2) - m) { - copy(this.#buf.subarray(this.#off), this.#buf); - } else if (c + n > MAX_SIZE) { - throw new Error("The buffer cannot be grown beyond the maximum size."); - } else { - const buf = new Uint8Array(Math.min(2 * c + n, MAX_SIZE)); - copy(this.#buf.subarray(this.#off), buf); - this.#buf = buf; - } - this.#off = 0; - this.#reslice(Math.min(m + n, MAX_SIZE)); - return m; - } - grow(n) { - if (n < 0) { - throw Error("Buffer.grow: negative count"); - } - const m = this.#grow(n); - this.#reslice(m); - } - async readFrom(r) { - let n = 0; - const tmp = new Uint8Array(MIN_READ); - while(true){ - const shouldGrow = this.capacity - this.length < MIN_READ; - const buf = shouldGrow ? tmp : new Uint8Array(this.#buf.buffer, this.length); - const nread = await r.read(buf); - if (nread === null) { - return n; - } - if (shouldGrow) this.writeSync(buf.subarray(0, nread)); - else this.#reslice(this.length + nread); - n += nread; - } - } - readFromSync(r) { - let n = 0; - const tmp = new Uint8Array(MIN_READ); - while(true){ - const shouldGrow = this.capacity - this.length < MIN_READ; - const buf = shouldGrow ? tmp : new Uint8Array(this.#buf.buffer, this.length); - const nread = r.readSync(buf); - if (nread === null) { - return n; - } - if (shouldGrow) this.writeSync(buf.subarray(0, nread)); - else this.#reslice(this.length + nread); - n += nread; - } - } -} -const BASE64_MAP = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r"; -function resolveYamlBinary(data) { - if (data === null) return false; - let code; - let bitlen = 0; - const max = data.length; - const map = BASE64_MAP; - for(let idx = 0; idx < max; idx++){ - code = map.indexOf(data.charAt(idx)); - if (code > 64) continue; - if (code < 0) return false; - bitlen += 6; - } - return bitlen % 8 === 0; -} -function constructYamlBinary(data) { - const input = data.replace(/[\r\n=]/g, ""); - const max = input.length; - const map = BASE64_MAP; - const result = []; - let bits = 0; - for(let idx = 0; idx < max; idx++){ - if (idx % 4 === 0 && idx) { - result.push(bits >> 16 & 0xff); - result.push(bits >> 8 & 0xff); - result.push(bits & 0xff); - } - bits = bits << 6 | map.indexOf(input.charAt(idx)); - } - const tailbits = max % 4 * 6; - if (tailbits === 0) { - result.push(bits >> 16 & 0xff); - result.push(bits >> 8 & 0xff); - result.push(bits & 0xff); - } else if (tailbits === 18) { - result.push(bits >> 10 & 0xff); - result.push(bits >> 2 & 0xff); - } else if (tailbits === 12) { - result.push(bits >> 4 & 0xff); - } - return new Buffer(new Uint8Array(result)); -} -function representYamlBinary(object) { - const max = object.length; - const map = BASE64_MAP; - let result = ""; - let bits = 0; - for(let idx = 0; idx < max; idx++){ - if (idx % 3 === 0 && idx) { - result += map[bits >> 18 & 0x3f]; - result += map[bits >> 12 & 0x3f]; - result += map[bits >> 6 & 0x3f]; - result += map[bits & 0x3f]; - } - bits = (bits << 8) + object[idx]; - } - const tail = max % 3; - if (tail === 0) { - result += map[bits >> 18 & 0x3f]; - result += map[bits >> 12 & 0x3f]; - result += map[bits >> 6 & 0x3f]; - result += map[bits & 0x3f]; - } else if (tail === 2) { - result += map[bits >> 10 & 0x3f]; - result += map[bits >> 4 & 0x3f]; - result += map[bits << 2 & 0x3f]; - result += map[64]; - } else if (tail === 1) { - result += map[bits >> 2 & 0x3f]; - result += map[bits << 4 & 0x3f]; - result += map[64]; - result += map[64]; - } - return result; -} -function isBinary(obj) { - const buf = new Buffer(); - try { - if (0 > buf.readFromSync(obj)) return true; - return false; - } catch { - return false; - } finally{ - buf.reset(); - } -} -const binary = new Type("tag:yaml.org,2002:binary", { - construct: constructYamlBinary, - kind: "scalar", - predicate: isBinary, - represent: representYamlBinary, - resolve: resolveYamlBinary -}); -function resolveYamlBoolean(data) { - const max = data.length; - return max === 4 && (data === "true" || data === "True" || data === "TRUE") || max === 5 && (data === "false" || data === "False" || data === "FALSE"); -} -function constructYamlBoolean(data) { - return data === "true" || data === "True" || data === "TRUE"; -} -const bool = new Type("tag:yaml.org,2002:bool", { - construct: constructYamlBoolean, - defaultStyle: "lowercase", - kind: "scalar", - predicate: isBoolean, - represent: { - lowercase (object) { - return object ? "true" : "false"; - }, - uppercase (object) { - return object ? "TRUE" : "FALSE"; - }, - camelcase (object) { - return object ? "True" : "False"; - } - }, - resolve: resolveYamlBoolean -}); -const YAML_FLOAT_PATTERN = new RegExp("^(?:[-+]?(?:0|[1-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?" + "|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?" + "|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*" + "|[-+]?\\.(?:inf|Inf|INF)" + "|\\.(?:nan|NaN|NAN))$"); -function resolveYamlFloat(data) { - if (!YAML_FLOAT_PATTERN.test(data) || data[data.length - 1] === "_") { - return false; - } - return true; -} -function constructYamlFloat(data) { - let value = data.replace(/_/g, "").toLowerCase(); - const sign = value[0] === "-" ? -1 : 1; - const digits = []; - if ("+-".indexOf(value[0]) >= 0) { - value = value.slice(1); - } - if (value === ".inf") { - return sign === 1 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY; - } - if (value === ".nan") { - return NaN; - } - if (value.indexOf(":") >= 0) { - value.split(":").forEach((v)=>{ - digits.unshift(parseFloat(v)); - }); - let valueNb = 0.0; - let base = 1; - digits.forEach((d)=>{ - valueNb += d * base; - base *= 60; - }); - return sign * valueNb; - } - return sign * parseFloat(value); -} -const SCIENTIFIC_WITHOUT_DOT = /^[-+]?[0-9]+e/; -function representYamlFloat(object, style) { - if (isNaN(object)) { - switch(style){ - case "lowercase": - return ".nan"; - case "uppercase": - return ".NAN"; - case "camelcase": - return ".NaN"; - } - } else if (Number.POSITIVE_INFINITY === object) { - switch(style){ - case "lowercase": - return ".inf"; - case "uppercase": - return ".INF"; - case "camelcase": - return ".Inf"; - } - } else if (Number.NEGATIVE_INFINITY === object) { - switch(style){ - case "lowercase": - return "-.inf"; - case "uppercase": - return "-.INF"; - case "camelcase": - return "-.Inf"; - } - } else if (isNegativeZero(object)) { - return "-0.0"; - } - const res = object.toString(10); - return SCIENTIFIC_WITHOUT_DOT.test(res) ? res.replace("e", ".e") : res; -} -function isFloat(object) { - return Object.prototype.toString.call(object) === "[object Number]" && (object % 1 !== 0 || isNegativeZero(object)); -} -const __float = new Type("tag:yaml.org,2002:float", { - construct: constructYamlFloat, - defaultStyle: "lowercase", - kind: "scalar", - predicate: isFloat, - represent: representYamlFloat, - resolve: resolveYamlFloat -}); -function reconstructFunction(code) { - const func = new Function(`return ${code}`)(); - if (!(func instanceof Function)) { - throw new TypeError(`Expected function but got ${typeof func}: ${code}`); - } - return func; -} -new Type("tag:yaml.org,2002:js/function", { - kind: "scalar", - resolve (data) { - if (data === null) { - return false; - } - try { - reconstructFunction(`${data}`); - return true; - } catch (_err) { - return false; - } - }, - construct (data) { - return reconstructFunction(data); - }, - predicate (object) { - return object instanceof Function; - }, - represent (object) { - return object.toString(); - } -}); -function isHexCode(c) { - return 0x30 <= c && c <= 0x39 || 0x41 <= c && c <= 0x46 || 0x61 <= c && c <= 0x66; -} -function isOctCode(c) { - return 0x30 <= c && c <= 0x37; -} -function isDecCode(c) { - return 0x30 <= c && c <= 0x39; -} -function resolveYamlInteger(data) { - const max = data.length; - let index = 0; - let hasDigits = false; - if (!max) return false; - let ch = data[index]; - if (ch === "-" || ch === "+") { - ch = data[++index]; - } - if (ch === "0") { - if (index + 1 === max) return true; - ch = data[++index]; - if (ch === "b") { - index++; - for(; index < max; index++){ - ch = data[index]; - if (ch === "_") continue; - if (ch !== "0" && ch !== "1") return false; - hasDigits = true; - } - return hasDigits && ch !== "_"; - } - if (ch === "x") { - index++; - for(; index < max; index++){ - ch = data[index]; - if (ch === "_") continue; - if (!isHexCode(data.charCodeAt(index))) return false; - hasDigits = true; - } - return hasDigits && ch !== "_"; - } - for(; index < max; index++){ - ch = data[index]; - if (ch === "_") continue; - if (!isOctCode(data.charCodeAt(index))) return false; - hasDigits = true; - } - return hasDigits && ch !== "_"; - } - if (ch === "_") return false; - for(; index < max; index++){ - ch = data[index]; - if (ch === "_") continue; - if (ch === ":") break; - if (!isDecCode(data.charCodeAt(index))) { - return false; - } - hasDigits = true; - } - if (!hasDigits || ch === "_") return false; - if (ch !== ":") return true; - return /^(:[0-5]?[0-9])+$/.test(data.slice(index)); -} -function constructYamlInteger(data) { - let value = data; - const digits = []; - if (value.indexOf("_") !== -1) { - value = value.replace(/_/g, ""); - } - let sign = 1; - let ch = value[0]; - if (ch === "-" || ch === "+") { - if (ch === "-") sign = -1; - value = value.slice(1); - ch = value[0]; - } - if (value === "0") return 0; - if (ch === "0") { - if (value[1] === "b") return sign * parseInt(value.slice(2), 2); - if (value[1] === "x") return sign * parseInt(value, 16); - return sign * parseInt(value, 8); - } - if (value.indexOf(":") !== -1) { - value.split(":").forEach((v)=>{ - digits.unshift(parseInt(v, 10)); - }); - let valueInt = 0; - let base = 1; - digits.forEach((d)=>{ - valueInt += d * base; - base *= 60; - }); - return sign * valueInt; - } - return sign * parseInt(value, 10); -} -function isInteger(object) { - return Object.prototype.toString.call(object) === "[object Number]" && object % 1 === 0 && !isNegativeZero(object); -} -const __int = new Type("tag:yaml.org,2002:int", { - construct: constructYamlInteger, - defaultStyle: "decimal", - kind: "scalar", - predicate: isInteger, - represent: { - binary (obj) { - return obj >= 0 ? `0b${obj.toString(2)}` : `-0b${obj.toString(2).slice(1)}`; - }, - octal (obj) { - return obj >= 0 ? `0${obj.toString(8)}` : `-0${obj.toString(8).slice(1)}`; - }, - decimal (obj) { - return obj.toString(10); - }, - hexadecimal (obj) { - return obj >= 0 ? `0x${obj.toString(16).toUpperCase()}` : `-0x${obj.toString(16).toUpperCase().slice(1)}`; - } - }, - resolve: resolveYamlInteger, - styleAliases: { - binary: [ - 2, - "bin" - ], - decimal: [ - 10, - "dec" - ], - hexadecimal: [ - 16, - "hex" - ], - octal: [ - 8, - "oct" - ] - } -}); -const map = new Type("tag:yaml.org,2002:map", { - construct (data) { - return data !== null ? data : {}; - }, - kind: "mapping" -}); -function resolveYamlMerge(data) { - return data === "<<" || data === null; -} -const merge = new Type("tag:yaml.org,2002:merge", { - kind: "scalar", - resolve: resolveYamlMerge -}); -function resolveYamlNull(data) { - const max = data.length; - return max === 1 && data === "~" || max === 4 && (data === "null" || data === "Null" || data === "NULL"); -} -function constructYamlNull() { - return null; -} -function isNull(object) { - return object === null; -} -const nil = new Type("tag:yaml.org,2002:null", { - construct: constructYamlNull, - defaultStyle: "lowercase", - kind: "scalar", - predicate: isNull, - represent: { - canonical () { - return "~"; - }, - lowercase () { - return "null"; - }, - uppercase () { - return "NULL"; - }, - camelcase () { - return "Null"; - } - }, - resolve: resolveYamlNull -}); -const { hasOwn } = Object; -const _toString = Object.prototype.toString; -function resolveYamlOmap(data) { - const objectKeys = []; - let pairKey = ""; - let pairHasKey = false; - for (const pair of data){ - pairHasKey = false; - if (_toString.call(pair) !== "[object Object]") return false; - for(pairKey in pair){ - if (hasOwn(pair, pairKey)) { - if (!pairHasKey) pairHasKey = true; - else return false; - } - } - if (!pairHasKey) return false; - if (objectKeys.indexOf(pairKey) === -1) objectKeys.push(pairKey); - else return false; - } - return true; -} -function constructYamlOmap(data) { - return data !== null ? data : []; -} -const omap = new Type("tag:yaml.org,2002:omap", { - construct: constructYamlOmap, - kind: "sequence", - resolve: resolveYamlOmap -}); -const _toString1 = Object.prototype.toString; -function resolveYamlPairs(data) { - const result = Array.from({ - length: data.length - }); - for(let index = 0; index < data.length; index++){ - const pair = data[index]; - if (_toString1.call(pair) !== "[object Object]") return false; - const keys = Object.keys(pair); - if (keys.length !== 1) return false; - result[index] = [ - keys[0], - pair[keys[0]] - ]; - } - return true; -} -function constructYamlPairs(data) { - if (data === null) return []; - const result = Array.from({ - length: data.length - }); - for(let index = 0; index < data.length; index += 1){ - const pair = data[index]; - const keys = Object.keys(pair); - result[index] = [ - keys[0], - pair[keys[0]] - ]; - } - return result; -} -const pairs = new Type("tag:yaml.org,2002:pairs", { - construct: constructYamlPairs, - kind: "sequence", - resolve: resolveYamlPairs -}); -const REGEXP = /^\/(?[\s\S]+)\/(?[gismuy]*)$/; -const regexp = new Type("tag:yaml.org,2002:js/regexp", { - kind: "scalar", - resolve (data) { - if (data === null || !data.length) { - return false; - } - const regexp = `${data}`; - if (regexp.charAt(0) === "/") { - if (!REGEXP.test(data)) { - return false; - } - const modifiers = [ - ...regexp.match(REGEXP)?.groups?.modifiers ?? "" - ]; - if (new Set(modifiers).size < modifiers.length) { - return false; - } - } - return true; - }, - construct (data) { - const { regexp = `${data}`, modifiers = "" } = `${data}`.match(REGEXP)?.groups ?? {}; - return new RegExp(regexp, modifiers); - }, - predicate (object) { - return object instanceof RegExp; - }, - represent (object) { - return object.toString(); - } -}); -const seq = new Type("tag:yaml.org,2002:seq", { - construct (data) { - return data !== null ? data : []; - }, - kind: "sequence" -}); -const { hasOwn: hasOwn1 } = Object; -function resolveYamlSet(data) { - if (data === null) return true; - for(const key in data){ - if (hasOwn1(data, key)) { - if (data[key] !== null) return false; - } - } - return true; -} -function constructYamlSet(data) { - return data !== null ? data : {}; -} -const set = new Type("tag:yaml.org,2002:set", { - construct: constructYamlSet, - kind: "mapping", - resolve: resolveYamlSet -}); -const str = new Type("tag:yaml.org,2002:str", { - construct (data) { - return data !== null ? data : ""; - }, - kind: "scalar" -}); -const YAML_DATE_REGEXP = new RegExp("^([0-9][0-9][0-9][0-9])" + "-([0-9][0-9])" + "-([0-9][0-9])$"); -const YAML_TIMESTAMP_REGEXP = new RegExp("^([0-9][0-9][0-9][0-9])" + "-([0-9][0-9]?)" + "-([0-9][0-9]?)" + "(?:[Tt]|[ \\t]+)" + "([0-9][0-9]?)" + ":([0-9][0-9])" + ":([0-9][0-9])" + "(?:\\.([0-9]*))?" + "(?:[ \\t]*(Z|([-+])([0-9][0-9]?)" + "(?::([0-9][0-9]))?))?$"); -function resolveYamlTimestamp(data) { - if (data === null) return false; - if (YAML_DATE_REGEXP.exec(data) !== null) return true; - if (YAML_TIMESTAMP_REGEXP.exec(data) !== null) return true; - return false; -} -function constructYamlTimestamp(data) { - let match = YAML_DATE_REGEXP.exec(data); - if (match === null) match = YAML_TIMESTAMP_REGEXP.exec(data); - if (match === null) throw new Error("Date resolve error"); - const year = +match[1]; - const month = +match[2] - 1; - const day = +match[3]; - if (!match[4]) { - return new Date(Date.UTC(year, month, day)); - } - const hour = +match[4]; - const minute = +match[5]; - const second = +match[6]; - let fraction = 0; - if (match[7]) { - let partFraction = match[7].slice(0, 3); - while(partFraction.length < 3){ - partFraction += "0"; - } - fraction = +partFraction; - } - let delta = null; - if (match[9]) { - const tzHour = +match[10]; - const tzMinute = +(match[11] || 0); - delta = (tzHour * 60 + tzMinute) * 60000; - if (match[9] === "-") delta = -delta; - } - const date = new Date(Date.UTC(year, month, day, hour, minute, second, fraction)); - if (delta) date.setTime(date.getTime() - delta); - return date; -} -function representYamlTimestamp(date) { - return date.toISOString(); -} -const timestamp = new Type("tag:yaml.org,2002:timestamp", { - construct: constructYamlTimestamp, - instanceOf: Date, - kind: "scalar", - represent: representYamlTimestamp, - resolve: resolveYamlTimestamp -}); -const undefinedType = new Type("tag:yaml.org,2002:js/undefined", { - kind: "scalar", - resolve () { - return true; - }, - construct () { - return undefined; - }, - predicate (object) { - return typeof object === "undefined"; - }, - represent () { - return ""; - } -}); -const failsafe = new Schema({ - explicit: [ - str, - seq, - map - ] -}); -const json = new Schema({ - implicit: [ - nil, - bool, - __int, - __float - ], - include: [ - failsafe - ] -}); -const core = new Schema({ - include: [ - json - ] -}); -const def = new Schema({ - explicit: [ - binary, - omap, - pairs, - set - ], - implicit: [ - timestamp, - merge - ], - include: [ - core - ] -}); -const extended = new Schema({ - explicit: [ - regexp, - undefinedType - ], - include: [ - def - ] -}); -class State { - schema; - constructor(schema = def){ - this.schema = schema; - } -} -class LoaderState extends State { - input; - documents; - length; - lineIndent; - lineStart; - position; - line; - filename; - onWarning; - legacy; - json; - listener; - implicitTypes; - typeMap; - version; - checkLineBreaks; - tagMap; - anchorMap; - tag; - anchor; - kind; - result; - constructor(input, { filename, schema, onWarning, legacy = false, json = false, listener = null }){ - super(schema); - this.input = input; - this.documents = []; - this.lineIndent = 0; - this.lineStart = 0; - this.position = 0; - this.line = 0; - this.result = ""; - this.filename = filename; - this.onWarning = onWarning; - this.legacy = legacy; - this.json = json; - this.listener = listener; - this.implicitTypes = this.schema.compiledImplicit; - this.typeMap = this.schema.compiledTypeMap; - this.length = input.length; - } -} -const { hasOwn: hasOwn2 } = Object; -const CONTEXT_BLOCK_IN = 3; -const CONTEXT_BLOCK_OUT = 4; -const CHOMPING_STRIP = 2; -const CHOMPING_KEEP = 3; -const PATTERN_NON_PRINTABLE = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/; -const PATTERN_NON_ASCII_LINE_BREAKS = /[\x85\u2028\u2029]/; -const PATTERN_FLOW_INDICATORS = /[,\[\]\{\}]/; -const PATTERN_TAG_HANDLE = /^(?:!|!!|![a-z\-]+!)$/i; -const PATTERN_TAG_URI = /^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/i; -function _class(obj) { - return Object.prototype.toString.call(obj); -} -function isEOL(c) { - return c === 0x0a || c === 0x0d; -} -function isWhiteSpace(c) { - return c === 0x09 || c === 0x20; -} -function isWsOrEol(c) { - return c === 0x09 || c === 0x20 || c === 0x0a || c === 0x0d; -} -function isFlowIndicator(c) { - return c === 0x2c || c === 0x5b || c === 0x5d || c === 0x7b || c === 0x7d; -} -function fromHexCode(c) { - if (0x30 <= c && c <= 0x39) { - return c - 0x30; - } - const lc = c | 0x20; - if (0x61 <= lc && lc <= 0x66) { - return lc - 0x61 + 10; - } - return -1; -} -function escapedHexLen(c) { - if (c === 0x78) { - return 2; - } - if (c === 0x75) { - return 4; - } - if (c === 0x55) { - return 8; - } - return 0; -} -function fromDecimalCode(c) { - if (0x30 <= c && c <= 0x39) { - return c - 0x30; - } - return -1; -} -function simpleEscapeSequence(c) { - return c === 0x30 ? "\x00" : c === 0x61 ? "\x07" : c === 0x62 ? "\x08" : c === 0x74 ? "\x09" : c === 0x09 ? "\x09" : c === 0x6e ? "\x0A" : c === 0x76 ? "\x0B" : c === 0x66 ? "\x0C" : c === 0x72 ? "\x0D" : c === 0x65 ? "\x1B" : c === 0x20 ? " " : c === 0x22 ? "\x22" : c === 0x2f ? "/" : c === 0x5c ? "\x5C" : c === 0x4e ? "\x85" : c === 0x5f ? "\xA0" : c === 0x4c ? "\u2028" : c === 0x50 ? "\u2029" : ""; -} -function charFromCodepoint(c) { - if (c <= 0xffff) { - return String.fromCharCode(c); - } - return String.fromCharCode((c - 0x010000 >> 10) + 0xd800, (c - 0x010000 & 0x03ff) + 0xdc00); -} -const simpleEscapeCheck = Array.from({ - length: 256 -}); -const simpleEscapeMap = Array.from({ - length: 256 -}); -for(let i = 0; i < 256; i++){ - simpleEscapeCheck[i] = simpleEscapeSequence(i) ? 1 : 0; - simpleEscapeMap[i] = simpleEscapeSequence(i); -} -function generateError(state, message) { - return new YAMLError(message, new Mark(state.filename, state.input, state.position, state.line, state.position - state.lineStart)); -} -function throwError(state, message) { - throw generateError(state, message); -} -function throwWarning(state, message) { - if (state.onWarning) { - state.onWarning.call(null, generateError(state, message)); - } -} -const directiveHandlers = { - YAML (state, _name, ...args) { - if (state.version !== null) { - return throwError(state, "duplication of %YAML directive"); - } - if (args.length !== 1) { - return throwError(state, "YAML directive accepts exactly one argument"); - } - const match = /^([0-9]+)\.([0-9]+)$/.exec(args[0]); - if (match === null) { - return throwError(state, "ill-formed argument of the YAML directive"); - } - const major = parseInt(match[1], 10); - const minor = parseInt(match[2], 10); - if (major !== 1) { - return throwError(state, "unacceptable YAML version of the document"); - } - state.version = args[0]; - state.checkLineBreaks = minor < 2; - if (minor !== 1 && minor !== 2) { - return throwWarning(state, "unsupported YAML version of the document"); - } - }, - TAG (state, _name, ...args) { - if (args.length !== 2) { - return throwError(state, "TAG directive accepts exactly two arguments"); - } - const handle = args[0]; - const prefix = args[1]; - if (!PATTERN_TAG_HANDLE.test(handle)) { - return throwError(state, "ill-formed tag handle (first argument) of the TAG directive"); - } - if (state.tagMap && hasOwn2(state.tagMap, handle)) { - return throwError(state, `there is a previously declared suffix for "${handle}" tag handle`); - } - if (!PATTERN_TAG_URI.test(prefix)) { - return throwError(state, "ill-formed tag prefix (second argument) of the TAG directive"); - } - if (typeof state.tagMap === "undefined") { - state.tagMap = {}; - } - state.tagMap[handle] = prefix; - } -}; -function captureSegment(state, start, end, checkJson) { - let result; - if (start < end) { - result = state.input.slice(start, end); - if (checkJson) { - for(let position = 0, length = result.length; position < length; position++){ - const character = result.charCodeAt(position); - if (!(character === 0x09 || 0x20 <= character && character <= 0x10ffff)) { - return throwError(state, "expected valid JSON character"); - } - } - } else if (PATTERN_NON_PRINTABLE.test(result)) { - return throwError(state, "the stream contains non-printable characters"); - } - state.result += result; - } -} -function mergeMappings(state, destination, source, overridableKeys) { - if (!isObject1(source)) { - return throwError(state, "cannot merge mappings; the provided source object is unacceptable"); - } - const keys = Object.keys(source); - for(let i = 0, len = keys.length; i < len; i++){ - const key = keys[i]; - if (!hasOwn2(destination, key)) { - destination[key] = source[key]; - overridableKeys[key] = true; - } - } -} -function storeMappingPair(state, result, overridableKeys, keyTag, keyNode, valueNode, startLine, startPos) { - if (Array.isArray(keyNode)) { - keyNode = Array.prototype.slice.call(keyNode); - for(let index = 0, quantity = keyNode.length; index < quantity; index++){ - if (Array.isArray(keyNode[index])) { - return throwError(state, "nested arrays are not supported inside keys"); - } - if (typeof keyNode === "object" && _class(keyNode[index]) === "[object Object]") { - keyNode[index] = "[object Object]"; - } - } - } - if (typeof keyNode === "object" && _class(keyNode) === "[object Object]") { - keyNode = "[object Object]"; - } - keyNode = String(keyNode); - if (result === null) { - result = {}; - } - if (keyTag === "tag:yaml.org,2002:merge") { - if (Array.isArray(valueNode)) { - for(let index = 0, quantity = valueNode.length; index < quantity; index++){ - mergeMappings(state, result, valueNode[index], overridableKeys); - } - } else { - mergeMappings(state, result, valueNode, overridableKeys); - } - } else { - if (!state.json && !hasOwn2(overridableKeys, keyNode) && hasOwn2(result, keyNode)) { - state.line = startLine || state.line; - state.position = startPos || state.position; - return throwError(state, "duplicated mapping key"); - } - result[keyNode] = valueNode; - delete overridableKeys[keyNode]; - } - return result; -} -function readLineBreak(state) { - const ch = state.input.charCodeAt(state.position); - if (ch === 0x0a) { - state.position++; - } else if (ch === 0x0d) { - state.position++; - if (state.input.charCodeAt(state.position) === 0x0a) { - state.position++; - } - } else { - return throwError(state, "a line break is expected"); - } - state.line += 1; - state.lineStart = state.position; -} -function skipSeparationSpace(state, allowComments, checkIndent) { - let lineBreaks = 0, ch = state.input.charCodeAt(state.position); - while(ch !== 0){ - while(isWhiteSpace(ch)){ - ch = state.input.charCodeAt(++state.position); - } - if (allowComments && ch === 0x23) { - do { - ch = state.input.charCodeAt(++state.position); - }while (ch !== 0x0a && ch !== 0x0d && ch !== 0) - } - if (isEOL(ch)) { - readLineBreak(state); - ch = state.input.charCodeAt(state.position); - lineBreaks++; - state.lineIndent = 0; - while(ch === 0x20){ - state.lineIndent++; - ch = state.input.charCodeAt(++state.position); - } - } else { - break; - } - } - if (checkIndent !== -1 && lineBreaks !== 0 && state.lineIndent < checkIndent) { - throwWarning(state, "deficient indentation"); - } - return lineBreaks; -} -function testDocumentSeparator(state) { - let _position = state.position; - let ch = state.input.charCodeAt(_position); - if ((ch === 0x2d || ch === 0x2e) && ch === state.input.charCodeAt(_position + 1) && ch === state.input.charCodeAt(_position + 2)) { - _position += 3; - ch = state.input.charCodeAt(_position); - if (ch === 0 || isWsOrEol(ch)) { - return true; - } - } - return false; -} -function writeFoldedLines(state, count) { - if (count === 1) { - state.result += " "; - } else if (count > 1) { - state.result += repeat("\n", count - 1); - } -} -function readPlainScalar(state, nodeIndent, withinFlowCollection) { - const kind = state.kind; - const result = state.result; - let ch = state.input.charCodeAt(state.position); - if (isWsOrEol(ch) || isFlowIndicator(ch) || ch === 0x23 || ch === 0x26 || ch === 0x2a || ch === 0x21 || ch === 0x7c || ch === 0x3e || ch === 0x27 || ch === 0x22 || ch === 0x25 || ch === 0x40 || ch === 0x60) { - return false; - } - let following; - if (ch === 0x3f || ch === 0x2d) { - following = state.input.charCodeAt(state.position + 1); - if (isWsOrEol(following) || withinFlowCollection && isFlowIndicator(following)) { - return false; - } - } - state.kind = "scalar"; - state.result = ""; - let captureEnd, captureStart = captureEnd = state.position; - let hasPendingContent = false; - let line = 0; - while(ch !== 0){ - if (ch === 0x3a) { - following = state.input.charCodeAt(state.position + 1); - if (isWsOrEol(following) || withinFlowCollection && isFlowIndicator(following)) { - break; - } - } else if (ch === 0x23) { - const preceding = state.input.charCodeAt(state.position - 1); - if (isWsOrEol(preceding)) { - break; - } - } else if (state.position === state.lineStart && testDocumentSeparator(state) || withinFlowCollection && isFlowIndicator(ch)) { - break; - } else if (isEOL(ch)) { - line = state.line; - const lineStart = state.lineStart; - const lineIndent = state.lineIndent; - skipSeparationSpace(state, false, -1); - if (state.lineIndent >= nodeIndent) { - hasPendingContent = true; - ch = state.input.charCodeAt(state.position); - continue; - } else { - state.position = captureEnd; - state.line = line; - state.lineStart = lineStart; - state.lineIndent = lineIndent; - break; - } - } - if (hasPendingContent) { - captureSegment(state, captureStart, captureEnd, false); - writeFoldedLines(state, state.line - line); - captureStart = captureEnd = state.position; - hasPendingContent = false; - } - if (!isWhiteSpace(ch)) { - captureEnd = state.position + 1; - } - ch = state.input.charCodeAt(++state.position); - } - captureSegment(state, captureStart, captureEnd, false); - if (state.result) { - return true; - } - state.kind = kind; - state.result = result; - return false; -} -function readSingleQuotedScalar(state, nodeIndent) { - let ch, captureStart, captureEnd; - ch = state.input.charCodeAt(state.position); - if (ch !== 0x27) { - return false; - } - state.kind = "scalar"; - state.result = ""; - state.position++; - captureStart = captureEnd = state.position; - while((ch = state.input.charCodeAt(state.position)) !== 0){ - if (ch === 0x27) { - captureSegment(state, captureStart, state.position, true); - ch = state.input.charCodeAt(++state.position); - if (ch === 0x27) { - captureStart = state.position; - state.position++; - captureEnd = state.position; - } else { - return true; - } - } else if (isEOL(ch)) { - captureSegment(state, captureStart, captureEnd, true); - writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent)); - captureStart = captureEnd = state.position; - } else if (state.position === state.lineStart && testDocumentSeparator(state)) { - return throwError(state, "unexpected end of the document within a single quoted scalar"); - } else { - state.position++; - captureEnd = state.position; - } - } - return throwError(state, "unexpected end of the stream within a single quoted scalar"); -} -function readDoubleQuotedScalar(state, nodeIndent) { - let ch = state.input.charCodeAt(state.position); - if (ch !== 0x22) { - return false; - } - state.kind = "scalar"; - state.result = ""; - state.position++; - let captureEnd, captureStart = captureEnd = state.position; - let tmp; - while((ch = state.input.charCodeAt(state.position)) !== 0){ - if (ch === 0x22) { - captureSegment(state, captureStart, state.position, true); - state.position++; - return true; - } - if (ch === 0x5c) { - captureSegment(state, captureStart, state.position, true); - ch = state.input.charCodeAt(++state.position); - if (isEOL(ch)) { - skipSeparationSpace(state, false, nodeIndent); - } else if (ch < 256 && simpleEscapeCheck[ch]) { - state.result += simpleEscapeMap[ch]; - state.position++; - } else if ((tmp = escapedHexLen(ch)) > 0) { - let hexLength = tmp; - let hexResult = 0; - for(; hexLength > 0; hexLength--){ - ch = state.input.charCodeAt(++state.position); - if ((tmp = fromHexCode(ch)) >= 0) { - hexResult = (hexResult << 4) + tmp; - } else { - return throwError(state, "expected hexadecimal character"); - } - } - state.result += charFromCodepoint(hexResult); - state.position++; - } else { - return throwError(state, "unknown escape sequence"); - } - captureStart = captureEnd = state.position; - } else if (isEOL(ch)) { - captureSegment(state, captureStart, captureEnd, true); - writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent)); - captureStart = captureEnd = state.position; - } else if (state.position === state.lineStart && testDocumentSeparator(state)) { - return throwError(state, "unexpected end of the document within a double quoted scalar"); - } else { - state.position++; - captureEnd = state.position; - } - } - return throwError(state, "unexpected end of the stream within a double quoted scalar"); -} -function readFlowCollection(state, nodeIndent) { - let ch = state.input.charCodeAt(state.position); - let terminator; - let isMapping = true; - let result = {}; - if (ch === 0x5b) { - terminator = 0x5d; - isMapping = false; - result = []; - } else if (ch === 0x7b) { - terminator = 0x7d; - } else { - return false; - } - if (state.anchor !== null && typeof state.anchor != "undefined" && typeof state.anchorMap != "undefined") { - state.anchorMap[state.anchor] = result; - } - ch = state.input.charCodeAt(++state.position); - const tag = state.tag, anchor = state.anchor; - let readNext = true; - let valueNode, keyNode, keyTag = keyNode = valueNode = null, isExplicitPair, isPair = isExplicitPair = false; - let following = 0, line = 0; - const overridableKeys = {}; - while(ch !== 0){ - skipSeparationSpace(state, true, nodeIndent); - ch = state.input.charCodeAt(state.position); - if (ch === terminator) { - state.position++; - state.tag = tag; - state.anchor = anchor; - state.kind = isMapping ? "mapping" : "sequence"; - state.result = result; - return true; - } - if (!readNext) { - return throwError(state, "missed comma between flow collection entries"); - } - keyTag = keyNode = valueNode = null; - isPair = isExplicitPair = false; - if (ch === 0x3f) { - following = state.input.charCodeAt(state.position + 1); - if (isWsOrEol(following)) { - isPair = isExplicitPair = true; - state.position++; - skipSeparationSpace(state, true, nodeIndent); - } - } - line = state.line; - composeNode(state, nodeIndent, 1, false, true); - keyTag = state.tag || null; - keyNode = state.result; - skipSeparationSpace(state, true, nodeIndent); - ch = state.input.charCodeAt(state.position); - if ((isExplicitPair || state.line === line) && ch === 0x3a) { - isPair = true; - ch = state.input.charCodeAt(++state.position); - skipSeparationSpace(state, true, nodeIndent); - composeNode(state, nodeIndent, 1, false, true); - valueNode = state.result; - } - if (isMapping) { - storeMappingPair(state, result, overridableKeys, keyTag, keyNode, valueNode); - } else if (isPair) { - result.push(storeMappingPair(state, null, overridableKeys, keyTag, keyNode, valueNode)); - } else { - result.push(keyNode); - } - skipSeparationSpace(state, true, nodeIndent); - ch = state.input.charCodeAt(state.position); - if (ch === 0x2c) { - readNext = true; - ch = state.input.charCodeAt(++state.position); - } else { - readNext = false; - } - } - return throwError(state, "unexpected end of the stream within a flow collection"); -} -function readBlockScalar(state, nodeIndent) { - let chomping = 1, didReadContent = false, detectedIndent = false, textIndent = nodeIndent, emptyLines = 0, atMoreIndented = false; - let ch = state.input.charCodeAt(state.position); - let folding = false; - if (ch === 0x7c) { - folding = false; - } else if (ch === 0x3e) { - folding = true; - } else { - return false; - } - state.kind = "scalar"; - state.result = ""; - let tmp = 0; - while(ch !== 0){ - ch = state.input.charCodeAt(++state.position); - if (ch === 0x2b || ch === 0x2d) { - if (1 === chomping) { - chomping = ch === 0x2b ? CHOMPING_KEEP : CHOMPING_STRIP; - } else { - return throwError(state, "repeat of a chomping mode identifier"); - } - } else if ((tmp = fromDecimalCode(ch)) >= 0) { - if (tmp === 0) { - return throwError(state, "bad explicit indentation width of a block scalar; it cannot be less than one"); - } else if (!detectedIndent) { - textIndent = nodeIndent + tmp - 1; - detectedIndent = true; - } else { - return throwError(state, "repeat of an indentation width identifier"); - } - } else { - break; - } - } - if (isWhiteSpace(ch)) { - do { - ch = state.input.charCodeAt(++state.position); - }while (isWhiteSpace(ch)) - if (ch === 0x23) { - do { - ch = state.input.charCodeAt(++state.position); - }while (!isEOL(ch) && ch !== 0) - } - } - while(ch !== 0){ - readLineBreak(state); - state.lineIndent = 0; - ch = state.input.charCodeAt(state.position); - while((!detectedIndent || state.lineIndent < textIndent) && ch === 0x20){ - state.lineIndent++; - ch = state.input.charCodeAt(++state.position); - } - if (!detectedIndent && state.lineIndent > textIndent) { - textIndent = state.lineIndent; - } - if (isEOL(ch)) { - emptyLines++; - continue; - } - if (state.lineIndent < textIndent) { - if (chomping === 3) { - state.result += repeat("\n", didReadContent ? 1 + emptyLines : emptyLines); - } else if (chomping === 1) { - if (didReadContent) { - state.result += "\n"; - } - } - break; - } - if (folding) { - if (isWhiteSpace(ch)) { - atMoreIndented = true; - state.result += repeat("\n", didReadContent ? 1 + emptyLines : emptyLines); - } else if (atMoreIndented) { - atMoreIndented = false; - state.result += repeat("\n", emptyLines + 1); - } else if (emptyLines === 0) { - if (didReadContent) { - state.result += " "; - } - } else { - state.result += repeat("\n", emptyLines); - } - } else { - state.result += repeat("\n", didReadContent ? 1 + emptyLines : emptyLines); - } - didReadContent = true; - detectedIndent = true; - emptyLines = 0; - const captureStart = state.position; - while(!isEOL(ch) && ch !== 0){ - ch = state.input.charCodeAt(++state.position); - } - captureSegment(state, captureStart, state.position, false); - } - return true; -} -function readBlockSequence(state, nodeIndent) { - let line, following, detected = false, ch; - const tag = state.tag, anchor = state.anchor, result = []; - if (state.anchor !== null && typeof state.anchor !== "undefined" && typeof state.anchorMap !== "undefined") { - state.anchorMap[state.anchor] = result; - } - ch = state.input.charCodeAt(state.position); - while(ch !== 0){ - if (ch !== 0x2d) { - break; - } - following = state.input.charCodeAt(state.position + 1); - if (!isWsOrEol(following)) { - break; - } - detected = true; - state.position++; - if (skipSeparationSpace(state, true, -1)) { - if (state.lineIndent <= nodeIndent) { - result.push(null); - ch = state.input.charCodeAt(state.position); - continue; - } - } - line = state.line; - composeNode(state, nodeIndent, 3, false, true); - result.push(state.result); - skipSeparationSpace(state, true, -1); - ch = state.input.charCodeAt(state.position); - if ((state.line === line || state.lineIndent > nodeIndent) && ch !== 0) { - return throwError(state, "bad indentation of a sequence entry"); - } else if (state.lineIndent < nodeIndent) { - break; - } - } - if (detected) { - state.tag = tag; - state.anchor = anchor; - state.kind = "sequence"; - state.result = result; - return true; - } - return false; -} -function readBlockMapping(state, nodeIndent, flowIndent) { - const tag = state.tag, anchor = state.anchor, result = {}, overridableKeys = {}; - let following, allowCompact = false, line, pos, keyTag = null, keyNode = null, valueNode = null, atExplicitKey = false, detected = false, ch; - if (state.anchor !== null && typeof state.anchor !== "undefined" && typeof state.anchorMap !== "undefined") { - state.anchorMap[state.anchor] = result; - } - ch = state.input.charCodeAt(state.position); - while(ch !== 0){ - following = state.input.charCodeAt(state.position + 1); - line = state.line; - pos = state.position; - if ((ch === 0x3f || ch === 0x3a) && isWsOrEol(following)) { - if (ch === 0x3f) { - if (atExplicitKey) { - storeMappingPair(state, result, overridableKeys, keyTag, keyNode, null); - keyTag = keyNode = valueNode = null; - } - detected = true; - atExplicitKey = true; - allowCompact = true; - } else if (atExplicitKey) { - atExplicitKey = false; - allowCompact = true; - } else { - return throwError(state, "incomplete explicit mapping pair; a key node is missed; or followed by a non-tabulated empty line"); - } - state.position += 1; - ch = following; - } else if (composeNode(state, flowIndent, 2, false, true)) { - if (state.line === line) { - ch = state.input.charCodeAt(state.position); - while(isWhiteSpace(ch)){ - ch = state.input.charCodeAt(++state.position); - } - if (ch === 0x3a) { - ch = state.input.charCodeAt(++state.position); - if (!isWsOrEol(ch)) { - return throwError(state, "a whitespace character is expected after the key-value separator within a block mapping"); - } - if (atExplicitKey) { - storeMappingPair(state, result, overridableKeys, keyTag, keyNode, null); - keyTag = keyNode = valueNode = null; - } - detected = true; - atExplicitKey = false; - allowCompact = false; - keyTag = state.tag; - keyNode = state.result; - } else if (detected) { - return throwError(state, "can not read an implicit mapping pair; a colon is missed"); - } else { - state.tag = tag; - state.anchor = anchor; - return true; - } - } else if (detected) { - return throwError(state, "can not read a block mapping entry; a multiline key may not be an implicit key"); - } else { - state.tag = tag; - state.anchor = anchor; - return true; - } - } else { - break; - } - if (state.line === line || state.lineIndent > nodeIndent) { - if (composeNode(state, nodeIndent, 4, true, allowCompact)) { - if (atExplicitKey) { - keyNode = state.result; - } else { - valueNode = state.result; - } - } - if (!atExplicitKey) { - storeMappingPair(state, result, overridableKeys, keyTag, keyNode, valueNode, line, pos); - keyTag = keyNode = valueNode = null; - } - skipSeparationSpace(state, true, -1); - ch = state.input.charCodeAt(state.position); - } - if (state.lineIndent > nodeIndent && ch !== 0) { - return throwError(state, "bad indentation of a mapping entry"); - } else if (state.lineIndent < nodeIndent) { - break; - } - } - if (atExplicitKey) { - storeMappingPair(state, result, overridableKeys, keyTag, keyNode, null); - } - if (detected) { - state.tag = tag; - state.anchor = anchor; - state.kind = "mapping"; - state.result = result; - } - return detected; -} -function readTagProperty(state) { - let position, isVerbatim = false, isNamed = false, tagHandle = "", tagName, ch; - ch = state.input.charCodeAt(state.position); - if (ch !== 0x21) return false; - if (state.tag !== null) { - return throwError(state, "duplication of a tag property"); - } - ch = state.input.charCodeAt(++state.position); - if (ch === 0x3c) { - isVerbatim = true; - ch = state.input.charCodeAt(++state.position); - } else if (ch === 0x21) { - isNamed = true; - tagHandle = "!!"; - ch = state.input.charCodeAt(++state.position); - } else { - tagHandle = "!"; - } - position = state.position; - if (isVerbatim) { - do { - ch = state.input.charCodeAt(++state.position); - }while (ch !== 0 && ch !== 0x3e) - if (state.position < state.length) { - tagName = state.input.slice(position, state.position); - ch = state.input.charCodeAt(++state.position); - } else { - return throwError(state, "unexpected end of the stream within a verbatim tag"); - } - } else { - while(ch !== 0 && !isWsOrEol(ch)){ - if (ch === 0x21) { - if (!isNamed) { - tagHandle = state.input.slice(position - 1, state.position + 1); - if (!PATTERN_TAG_HANDLE.test(tagHandle)) { - return throwError(state, "named tag handle cannot contain such characters"); - } - isNamed = true; - position = state.position + 1; - } else { - return throwError(state, "tag suffix cannot contain exclamation marks"); - } - } - ch = state.input.charCodeAt(++state.position); - } - tagName = state.input.slice(position, state.position); - if (PATTERN_FLOW_INDICATORS.test(tagName)) { - return throwError(state, "tag suffix cannot contain flow indicator characters"); - } - } - if (tagName && !PATTERN_TAG_URI.test(tagName)) { - return throwError(state, `tag name cannot contain such characters: ${tagName}`); - } - if (isVerbatim) { - state.tag = tagName; - } else if (typeof state.tagMap !== "undefined" && hasOwn2(state.tagMap, tagHandle)) { - state.tag = state.tagMap[tagHandle] + tagName; - } else if (tagHandle === "!") { - state.tag = `!${tagName}`; - } else if (tagHandle === "!!") { - state.tag = `tag:yaml.org,2002:${tagName}`; - } else { - return throwError(state, `undeclared tag handle "${tagHandle}"`); - } - return true; -} -function readAnchorProperty(state) { - let ch = state.input.charCodeAt(state.position); - if (ch !== 0x26) return false; - if (state.anchor !== null) { - return throwError(state, "duplication of an anchor property"); - } - ch = state.input.charCodeAt(++state.position); - const position = state.position; - while(ch !== 0 && !isWsOrEol(ch) && !isFlowIndicator(ch)){ - ch = state.input.charCodeAt(++state.position); - } - if (state.position === position) { - return throwError(state, "name of an anchor node must contain at least one character"); - } - state.anchor = state.input.slice(position, state.position); - return true; -} -function readAlias(state) { - let ch = state.input.charCodeAt(state.position); - if (ch !== 0x2a) return false; - ch = state.input.charCodeAt(++state.position); - const _position = state.position; - while(ch !== 0 && !isWsOrEol(ch) && !isFlowIndicator(ch)){ - ch = state.input.charCodeAt(++state.position); - } - if (state.position === _position) { - return throwError(state, "name of an alias node must contain at least one character"); - } - const alias = state.input.slice(_position, state.position); - if (typeof state.anchorMap !== "undefined" && !hasOwn2(state.anchorMap, alias)) { - return throwError(state, `unidentified alias "${alias}"`); - } - if (typeof state.anchorMap !== "undefined") { - state.result = state.anchorMap[alias]; - } - skipSeparationSpace(state, true, -1); - return true; -} -function composeNode(state, parentIndent, nodeContext, allowToSeek, allowCompact) { - let allowBlockScalars, allowBlockCollections, indentStatus = 1, atNewLine = false, hasContent = false, type, flowIndent, blockIndent; - if (state.listener && state.listener !== null) { - state.listener("open", state); - } - state.tag = null; - state.anchor = null; - state.kind = null; - state.result = null; - const allowBlockStyles = allowBlockScalars = allowBlockCollections = CONTEXT_BLOCK_OUT === nodeContext || CONTEXT_BLOCK_IN === nodeContext; - if (allowToSeek) { - if (skipSeparationSpace(state, true, -1)) { - atNewLine = true; - if (state.lineIndent > parentIndent) { - indentStatus = 1; - } else if (state.lineIndent === parentIndent) { - indentStatus = 0; - } else if (state.lineIndent < parentIndent) { - indentStatus = -1; - } - } - } - if (indentStatus === 1) { - while(readTagProperty(state) || readAnchorProperty(state)){ - if (skipSeparationSpace(state, true, -1)) { - atNewLine = true; - allowBlockCollections = allowBlockStyles; - if (state.lineIndent > parentIndent) { - indentStatus = 1; - } else if (state.lineIndent === parentIndent) { - indentStatus = 0; - } else if (state.lineIndent < parentIndent) { - indentStatus = -1; - } - } else { - allowBlockCollections = false; - } - } - } - if (allowBlockCollections) { - allowBlockCollections = atNewLine || allowCompact; - } - if (indentStatus === 1 || 4 === nodeContext) { - const cond = 1 === nodeContext || 2 === nodeContext; - flowIndent = cond ? parentIndent : parentIndent + 1; - blockIndent = state.position - state.lineStart; - if (indentStatus === 1) { - if (allowBlockCollections && (readBlockSequence(state, blockIndent) || readBlockMapping(state, blockIndent, flowIndent)) || readFlowCollection(state, flowIndent)) { - hasContent = true; - } else { - if (allowBlockScalars && readBlockScalar(state, flowIndent) || readSingleQuotedScalar(state, flowIndent) || readDoubleQuotedScalar(state, flowIndent)) { - hasContent = true; - } else if (readAlias(state)) { - hasContent = true; - if (state.tag !== null || state.anchor !== null) { - return throwError(state, "alias node should not have Any properties"); - } - } else if (readPlainScalar(state, flowIndent, 1 === nodeContext)) { - hasContent = true; - if (state.tag === null) { - state.tag = "?"; - } - } - if (state.anchor !== null && typeof state.anchorMap !== "undefined") { - state.anchorMap[state.anchor] = state.result; - } - } - } else if (indentStatus === 0) { - hasContent = allowBlockCollections && readBlockSequence(state, blockIndent); - } - } - if (state.tag !== null && state.tag !== "!") { - if (state.tag === "?") { - for(let typeIndex = 0, typeQuantity = state.implicitTypes.length; typeIndex < typeQuantity; typeIndex++){ - type = state.implicitTypes[typeIndex]; - if (type.resolve(state.result)) { - state.result = type.construct(state.result); - state.tag = type.tag; - if (state.anchor !== null && typeof state.anchorMap !== "undefined") { - state.anchorMap[state.anchor] = state.result; - } - break; - } - } - } else if (hasOwn2(state.typeMap[state.kind || "fallback"], state.tag)) { - type = state.typeMap[state.kind || "fallback"][state.tag]; - if (state.result !== null && type.kind !== state.kind) { - return throwError(state, `unacceptable node kind for !<${state.tag}> tag; it should be "${type.kind}", not "${state.kind}"`); - } - if (!type.resolve(state.result)) { - return throwError(state, `cannot resolve a node with !<${state.tag}> explicit tag`); - } else { - state.result = type.construct(state.result); - if (state.anchor !== null && typeof state.anchorMap !== "undefined") { - state.anchorMap[state.anchor] = state.result; - } - } - } else { - return throwError(state, `unknown tag !<${state.tag}>`); - } - } - if (state.listener && state.listener !== null) { - state.listener("close", state); - } - return state.tag !== null || state.anchor !== null || hasContent; -} -function readDocument(state) { - const documentStart = state.position; - let position, directiveName, directiveArgs, hasDirectives = false, ch; - state.version = null; - state.checkLineBreaks = state.legacy; - state.tagMap = {}; - state.anchorMap = {}; - while((ch = state.input.charCodeAt(state.position)) !== 0){ - skipSeparationSpace(state, true, -1); - ch = state.input.charCodeAt(state.position); - if (state.lineIndent > 0 || ch !== 0x25) { - break; - } - hasDirectives = true; - ch = state.input.charCodeAt(++state.position); - position = state.position; - while(ch !== 0 && !isWsOrEol(ch)){ - ch = state.input.charCodeAt(++state.position); - } - directiveName = state.input.slice(position, state.position); - directiveArgs = []; - if (directiveName.length < 1) { - return throwError(state, "directive name must not be less than one character in length"); - } - while(ch !== 0){ - while(isWhiteSpace(ch)){ - ch = state.input.charCodeAt(++state.position); - } - if (ch === 0x23) { - do { - ch = state.input.charCodeAt(++state.position); - }while (ch !== 0 && !isEOL(ch)) - break; - } - if (isEOL(ch)) break; - position = state.position; - while(ch !== 0 && !isWsOrEol(ch)){ - ch = state.input.charCodeAt(++state.position); - } - directiveArgs.push(state.input.slice(position, state.position)); - } - if (ch !== 0) readLineBreak(state); - if (hasOwn2(directiveHandlers, directiveName)) { - directiveHandlers[directiveName](state, directiveName, ...directiveArgs); - } else { - throwWarning(state, `unknown document directive "${directiveName}"`); - } - } - skipSeparationSpace(state, true, -1); - if (state.lineIndent === 0 && state.input.charCodeAt(state.position) === 0x2d && state.input.charCodeAt(state.position + 1) === 0x2d && state.input.charCodeAt(state.position + 2) === 0x2d) { - state.position += 3; - skipSeparationSpace(state, true, -1); - } else if (hasDirectives) { - return throwError(state, "directives end mark is expected"); - } - composeNode(state, state.lineIndent - 1, 4, false, true); - skipSeparationSpace(state, true, -1); - if (state.checkLineBreaks && PATTERN_NON_ASCII_LINE_BREAKS.test(state.input.slice(documentStart, state.position))) { - throwWarning(state, "non-ASCII line breaks are interpreted as content"); - } - state.documents.push(state.result); - if (state.position === state.lineStart && testDocumentSeparator(state)) { - if (state.input.charCodeAt(state.position) === 0x2e) { - state.position += 3; - skipSeparationSpace(state, true, -1); - } - return; - } - if (state.position < state.length - 1) { - return throwError(state, "end of the stream or a document separator is expected"); - } else { - return; - } -} -function loadDocuments(input, options) { - input = String(input); - options = options || {}; - if (input.length !== 0) { - if (input.charCodeAt(input.length - 1) !== 0x0a && input.charCodeAt(input.length - 1) !== 0x0d) { - input += "\n"; - } - if (input.charCodeAt(0) === 0xfeff) { - input = input.slice(1); - } - } - const state = new LoaderState(input, options); - state.input += "\0"; - while(state.input.charCodeAt(state.position) === 0x20){ - state.lineIndent += 1; - state.position += 1; - } - while(state.position < state.length - 1){ - readDocument(state); - } - return state.documents; -} -function isCbFunction(fn) { - return typeof fn === "function"; -} -function loadAll(input, iteratorOrOption, options) { - if (!isCbFunction(iteratorOrOption)) { - return loadDocuments(input, iteratorOrOption); - } - const documents = loadDocuments(input, options); - const iterator = iteratorOrOption; - for(let index = 0, length = documents.length; index < length; index++){ - iterator(documents[index]); - } - return void 0; -} -function load(input, options) { - const documents = loadDocuments(input, options); - if (documents.length === 0) { - return; - } - if (documents.length === 1) { - return documents[0]; - } - throw new YAMLError("expected a single document in the stream, but found more"); -} -function parse(content, options) { - return load(content, options); -} -function parseAll(content, iterator, options) { - return loadAll(content, iterator, options); -} -const { hasOwn: hasOwn3 } = Object; -function compileStyleMap(schema, map) { - if (typeof map === "undefined" || map === null) return {}; - let type; - const result = {}; - const keys = Object.keys(map); - let tag, style; - for(let index = 0, length = keys.length; index < length; index += 1){ - tag = keys[index]; - style = String(map[tag]); - if (tag.slice(0, 2) === "!!") { - tag = `tag:yaml.org,2002:${tag.slice(2)}`; - } - type = schema.compiledTypeMap.fallback[tag]; - if (type && typeof type.styleAliases !== "undefined" && hasOwn3(type.styleAliases, style)) { - style = type.styleAliases[style]; - } - result[tag] = style; - } - return result; -} -class DumperState extends State { - indent; - noArrayIndent; - skipInvalid; - flowLevel; - sortKeys; - lineWidth; - noRefs; - noCompatMode; - condenseFlow; - implicitTypes; - explicitTypes; - tag = null; - result = ""; - duplicates = []; - usedDuplicates = []; - styleMap; - dump; - constructor({ schema, indent = 2, noArrayIndent = false, skipInvalid = false, flowLevel = -1, styles = null, sortKeys = false, lineWidth = 80, noRefs = false, noCompatMode = false, condenseFlow = false }){ - super(schema); - this.indent = Math.max(1, indent); - this.noArrayIndent = noArrayIndent; - this.skipInvalid = skipInvalid; - this.flowLevel = flowLevel; - this.styleMap = compileStyleMap(this.schema, styles); - this.sortKeys = sortKeys; - this.lineWidth = lineWidth; - this.noRefs = noRefs; - this.noCompatMode = noCompatMode; - this.condenseFlow = condenseFlow; - this.implicitTypes = this.schema.compiledImplicit; - this.explicitTypes = this.schema.compiledExplicit; - } -} -const _toString2 = Object.prototype.toString; -const { hasOwn: hasOwn4 } = Object; -const ESCAPE_SEQUENCES = {}; -ESCAPE_SEQUENCES[0x00] = "\\0"; -ESCAPE_SEQUENCES[0x07] = "\\a"; -ESCAPE_SEQUENCES[0x08] = "\\b"; -ESCAPE_SEQUENCES[0x09] = "\\t"; -ESCAPE_SEQUENCES[0x0a] = "\\n"; -ESCAPE_SEQUENCES[0x0b] = "\\v"; -ESCAPE_SEQUENCES[0x0c] = "\\f"; -ESCAPE_SEQUENCES[0x0d] = "\\r"; -ESCAPE_SEQUENCES[0x1b] = "\\e"; -ESCAPE_SEQUENCES[0x22] = '\\"'; -ESCAPE_SEQUENCES[0x5c] = "\\\\"; -ESCAPE_SEQUENCES[0x85] = "\\N"; -ESCAPE_SEQUENCES[0xa0] = "\\_"; -ESCAPE_SEQUENCES[0x2028] = "\\L"; -ESCAPE_SEQUENCES[0x2029] = "\\P"; -const DEPRECATED_BOOLEANS_SYNTAX = [ - "y", - "Y", - "yes", - "Yes", - "YES", - "on", - "On", - "ON", - "n", - "N", - "no", - "No", - "NO", - "off", - "Off", - "OFF" -]; -function encodeHex(character) { - const string = character.toString(16).toUpperCase(); - let handle; - let length; - if (character <= 0xff) { - handle = "x"; - length = 2; - } else if (character <= 0xffff) { - handle = "u"; - length = 4; - } else if (character <= 0xffffffff) { - handle = "U"; - length = 8; - } else { - throw new YAMLError("code point within a string may not be greater than 0xFFFFFFFF"); - } - return `\\${handle}${repeat("0", length - string.length)}${string}`; -} -function indentString(string, spaces) { - const ind = repeat(" ", spaces), length = string.length; - let position = 0, next = -1, result = "", line; - while(position < length){ - next = string.indexOf("\n", position); - if (next === -1) { - line = string.slice(position); - position = length; - } else { - line = string.slice(position, next + 1); - position = next + 1; - } - if (line.length && line !== "\n") result += ind; - result += line; - } - return result; -} -function generateNextLine(state, level) { - return `\n${repeat(" ", state.indent * level)}`; -} -function testImplicitResolving(state, str) { - let type; - for(let index = 0, length = state.implicitTypes.length; index < length; index += 1){ - type = state.implicitTypes[index]; - if (type.resolve(str)) { - return true; - } - } - return false; -} -function isWhitespace(c) { - return c === 0x20 || c === 0x09; -} -function isPrintable(c) { - return 0x00020 <= c && c <= 0x00007e || 0x000a1 <= c && c <= 0x00d7ff && c !== 0x2028 && c !== 0x2029 || 0x0e000 <= c && c <= 0x00fffd && c !== 0xfeff || 0x10000 <= c && c <= 0x10ffff; -} -function isPlainSafe(c) { - return isPrintable(c) && c !== 0xfeff && c !== 0x2c && c !== 0x5b && c !== 0x5d && c !== 0x7b && c !== 0x7d && c !== 0x3a && c !== 0x23; -} -function isPlainSafeFirst(c) { - return isPrintable(c) && c !== 0xfeff && !isWhitespace(c) && c !== 0x2d && c !== 0x3f && c !== 0x3a && c !== 0x2c && c !== 0x5b && c !== 0x5d && c !== 0x7b && c !== 0x7d && c !== 0x23 && c !== 0x26 && c !== 0x2a && c !== 0x21 && c !== 0x7c && c !== 0x3e && c !== 0x27 && c !== 0x22 && c !== 0x25 && c !== 0x40 && c !== 0x60; -} -function needIndentIndicator(string) { - const leadingSpaceRe = /^\n* /; - return leadingSpaceRe.test(string); -} -const STYLE_PLAIN = 1, STYLE_SINGLE = 2, STYLE_LITERAL = 3, STYLE_FOLDED = 4, STYLE_DOUBLE = 5; -function chooseScalarStyle(string, singleLineOnly, indentPerLevel, lineWidth, testAmbiguousType) { - const shouldTrackWidth = lineWidth !== -1; - let hasLineBreak = false, hasFoldableLine = false, previousLineBreak = -1, plain = isPlainSafeFirst(string.charCodeAt(0)) && !isWhitespace(string.charCodeAt(string.length - 1)); - let __char, i; - if (singleLineOnly) { - for(i = 0; i < string.length; i++){ - __char = string.charCodeAt(i); - if (!isPrintable(__char)) { - return 5; - } - plain = plain && isPlainSafe(__char); - } - } else { - for(i = 0; i < string.length; i++){ - __char = string.charCodeAt(i); - if (__char === 0x0a) { - hasLineBreak = true; - if (shouldTrackWidth) { - hasFoldableLine = hasFoldableLine || i - previousLineBreak - 1 > lineWidth && string[previousLineBreak + 1] !== " "; - previousLineBreak = i; - } - } else if (!isPrintable(__char)) { - return 5; - } - plain = plain && isPlainSafe(__char); - } - hasFoldableLine = hasFoldableLine || shouldTrackWidth && i - previousLineBreak - 1 > lineWidth && string[previousLineBreak + 1] !== " "; - } - if (!hasLineBreak && !hasFoldableLine) { - return plain && !testAmbiguousType(string) ? 1 : 2; - } - if (indentPerLevel > 9 && needIndentIndicator(string)) { - return 5; - } - return hasFoldableLine ? 4 : 3; -} -function foldLine(line, width) { - if (line === "" || line[0] === " ") return line; - const breakRe = / [^ ]/g; - let match; - let start = 0, end, curr = 0, next = 0; - let result = ""; - while(match = breakRe.exec(line)){ - next = match.index; - if (next - start > width) { - end = curr > start ? curr : next; - result += `\n${line.slice(start, end)}`; - start = end + 1; - } - curr = next; - } - result += "\n"; - if (line.length - start > width && curr > start) { - result += `${line.slice(start, curr)}\n${line.slice(curr + 1)}`; - } else { - result += line.slice(start); - } - return result.slice(1); -} -function dropEndingNewline(string) { - return string[string.length - 1] === "\n" ? string.slice(0, -1) : string; -} -function foldString(string, width) { - const lineRe = /(\n+)([^\n]*)/g; - let result = (()=>{ - let nextLF = string.indexOf("\n"); - nextLF = nextLF !== -1 ? nextLF : string.length; - lineRe.lastIndex = nextLF; - return foldLine(string.slice(0, nextLF), width); - })(); - let prevMoreIndented = string[0] === "\n" || string[0] === " "; - let moreIndented; - let match; - while(match = lineRe.exec(string)){ - const prefix = match[1], line = match[2]; - moreIndented = line[0] === " "; - result += prefix + (!prevMoreIndented && !moreIndented && line !== "" ? "\n" : "") + foldLine(line, width); - prevMoreIndented = moreIndented; - } - return result; -} -function escapeString(string) { - let result = ""; - let __char, nextChar; - let escapeSeq; - for(let i = 0; i < string.length; i++){ - __char = string.charCodeAt(i); - if (__char >= 0xd800 && __char <= 0xdbff) { - nextChar = string.charCodeAt(i + 1); - if (nextChar >= 0xdc00 && nextChar <= 0xdfff) { - result += encodeHex((__char - 0xd800) * 0x400 + nextChar - 0xdc00 + 0x10000); - i++; - continue; - } - } - escapeSeq = ESCAPE_SEQUENCES[__char]; - result += !escapeSeq && isPrintable(__char) ? string[i] : escapeSeq || encodeHex(__char); - } - return result; -} -function blockHeader(string, indentPerLevel) { - const indentIndicator = needIndentIndicator(string) ? String(indentPerLevel) : ""; - const clip = string[string.length - 1] === "\n"; - const keep = clip && (string[string.length - 2] === "\n" || string === "\n"); - const chomp = keep ? "+" : clip ? "" : "-"; - return `${indentIndicator}${chomp}\n`; -} -function writeScalar(state, string, level, iskey) { - state.dump = (()=>{ - if (string.length === 0) { - return "''"; - } - if (!state.noCompatMode && DEPRECATED_BOOLEANS_SYNTAX.indexOf(string) !== -1) { - return `'${string}'`; - } - const indent = state.indent * Math.max(1, level); - const lineWidth = state.lineWidth === -1 ? -1 : Math.max(Math.min(state.lineWidth, 40), state.lineWidth - indent); - const singleLineOnly = iskey || state.flowLevel > -1 && level >= state.flowLevel; - function testAmbiguity(str) { - return testImplicitResolving(state, str); - } - switch(chooseScalarStyle(string, singleLineOnly, state.indent, lineWidth, testAmbiguity)){ - case STYLE_PLAIN: - return string; - case STYLE_SINGLE: - return `'${string.replace(/'/g, "''")}'`; - case STYLE_LITERAL: - return `|${blockHeader(string, state.indent)}${dropEndingNewline(indentString(string, indent))}`; - case STYLE_FOLDED: - return `>${blockHeader(string, state.indent)}${dropEndingNewline(indentString(foldString(string, lineWidth), indent))}`; - case STYLE_DOUBLE: - return `"${escapeString(string)}"`; - default: - throw new YAMLError("impossible error: invalid scalar style"); - } - })(); -} -function writeFlowSequence(state, level, object) { - let _result = ""; - const _tag = state.tag; - for(let index = 0, length = object.length; index < length; index += 1){ - if (writeNode(state, level, object[index], false, false)) { - if (index !== 0) _result += `,${!state.condenseFlow ? " " : ""}`; - _result += state.dump; - } - } - state.tag = _tag; - state.dump = `[${_result}]`; -} -function writeBlockSequence(state, level, object, compact = false) { - let _result = ""; - const _tag = state.tag; - for(let index = 0, length = object.length; index < length; index += 1){ - if (writeNode(state, level + 1, object[index], true, true)) { - if (!compact || index !== 0) { - _result += generateNextLine(state, level); - } - if (state.dump && 0x0a === state.dump.charCodeAt(0)) { - _result += "-"; - } else { - _result += "- "; - } - _result += state.dump; - } - } - state.tag = _tag; - state.dump = _result || "[]"; -} -function writeFlowMapping(state, level, object) { - let _result = ""; - const _tag = state.tag, objectKeyList = Object.keys(object); - let pairBuffer, objectKey, objectValue; - for(let index = 0, length = objectKeyList.length; index < length; index += 1){ - pairBuffer = state.condenseFlow ? '"' : ""; - if (index !== 0) pairBuffer += ", "; - objectKey = objectKeyList[index]; - objectValue = object[objectKey]; - if (!writeNode(state, level, objectKey, false, false)) { - continue; - } - if (state.dump.length > 1024) pairBuffer += "? "; - pairBuffer += `${state.dump}${state.condenseFlow ? '"' : ""}:${state.condenseFlow ? "" : " "}`; - if (!writeNode(state, level, objectValue, false, false)) { - continue; - } - pairBuffer += state.dump; - _result += pairBuffer; - } - state.tag = _tag; - state.dump = `{${_result}}`; -} -function writeBlockMapping(state, level, object, compact = false) { - const _tag = state.tag, objectKeyList = Object.keys(object); - let _result = ""; - if (state.sortKeys === true) { - objectKeyList.sort(); - } else if (typeof state.sortKeys === "function") { - objectKeyList.sort(state.sortKeys); - } else if (state.sortKeys) { - throw new YAMLError("sortKeys must be a boolean or a function"); - } - let pairBuffer = "", objectKey, objectValue, explicitPair; - for(let index = 0, length = objectKeyList.length; index < length; index += 1){ - pairBuffer = ""; - if (!compact || index !== 0) { - pairBuffer += generateNextLine(state, level); - } - objectKey = objectKeyList[index]; - objectValue = object[objectKey]; - if (!writeNode(state, level + 1, objectKey, true, true, true)) { - continue; - } - explicitPair = state.tag !== null && state.tag !== "?" || state.dump && state.dump.length > 1024; - if (explicitPair) { - if (state.dump && 0x0a === state.dump.charCodeAt(0)) { - pairBuffer += "?"; - } else { - pairBuffer += "? "; - } - } - pairBuffer += state.dump; - if (explicitPair) { - pairBuffer += generateNextLine(state, level); - } - if (!writeNode(state, level + 1, objectValue, true, explicitPair)) { - continue; - } - if (state.dump && 0x0a === state.dump.charCodeAt(0)) { - pairBuffer += ":"; - } else { - pairBuffer += ": "; - } - pairBuffer += state.dump; - _result += pairBuffer; - } - state.tag = _tag; - state.dump = _result || "{}"; -} -function detectType(state, object, explicit = false) { - const typeList = explicit ? state.explicitTypes : state.implicitTypes; - let type; - let style; - let _result; - for(let index = 0, length = typeList.length; index < length; index += 1){ - type = typeList[index]; - if ((type.instanceOf || type.predicate) && (!type.instanceOf || typeof object === "object" && object instanceof type.instanceOf) && (!type.predicate || type.predicate(object))) { - state.tag = explicit ? type.tag : "?"; - if (type.represent) { - style = state.styleMap[type.tag] || type.defaultStyle; - if (_toString2.call(type.represent) === "[object Function]") { - _result = type.represent(object, style); - } else if (hasOwn4(type.represent, style)) { - _result = type.represent[style](object, style); - } else { - throw new YAMLError(`!<${type.tag}> tag resolver accepts not "${style}" style`); - } - state.dump = _result; - } - return true; - } - } - return false; -} -function writeNode(state, level, object, block, compact, iskey = false) { - state.tag = null; - state.dump = object; - if (!detectType(state, object, false)) { - detectType(state, object, true); - } - const type = _toString2.call(state.dump); - if (block) { - block = state.flowLevel < 0 || state.flowLevel > level; - } - const objectOrArray = type === "[object Object]" || type === "[object Array]"; - let duplicateIndex = -1; - let duplicate = false; - if (objectOrArray) { - duplicateIndex = state.duplicates.indexOf(object); - duplicate = duplicateIndex !== -1; - } - if (state.tag !== null && state.tag !== "?" || duplicate || state.indent !== 2 && level > 0) { - compact = false; - } - if (duplicate && state.usedDuplicates[duplicateIndex]) { - state.dump = `*ref_${duplicateIndex}`; - } else { - if (objectOrArray && duplicate && !state.usedDuplicates[duplicateIndex]) { - state.usedDuplicates[duplicateIndex] = true; - } - if (type === "[object Object]") { - if (block && Object.keys(state.dump).length !== 0) { - writeBlockMapping(state, level, state.dump, compact); - if (duplicate) { - state.dump = `&ref_${duplicateIndex}${state.dump}`; - } - } else { - writeFlowMapping(state, level, state.dump); - if (duplicate) { - state.dump = `&ref_${duplicateIndex} ${state.dump}`; - } - } - } else if (type === "[object Array]") { - const arrayLevel = state.noArrayIndent && level > 0 ? level - 1 : level; - if (block && state.dump.length !== 0) { - writeBlockSequence(state, arrayLevel, state.dump, compact); - if (duplicate) { - state.dump = `&ref_${duplicateIndex}${state.dump}`; - } - } else { - writeFlowSequence(state, arrayLevel, state.dump); - if (duplicate) { - state.dump = `&ref_${duplicateIndex} ${state.dump}`; - } - } - } else if (type === "[object String]") { - if (state.tag !== "?") { - writeScalar(state, state.dump, level, iskey); - } - } else { - if (state.skipInvalid) return false; - throw new YAMLError(`unacceptable kind of an object to dump ${type}`); - } - if (state.tag !== null && state.tag !== "?") { - state.dump = `!<${state.tag}> ${state.dump}`; - } - } - return true; -} -function inspectNode(object, objects, duplicatesIndexes) { - if (object !== null && typeof object === "object") { - const index = objects.indexOf(object); - if (index !== -1) { - if (duplicatesIndexes.indexOf(index) === -1) { - duplicatesIndexes.push(index); - } - } else { - objects.push(object); - if (Array.isArray(object)) { - for(let idx = 0, length = object.length; idx < length; idx += 1){ - inspectNode(object[idx], objects, duplicatesIndexes); - } - } else { - const objectKeyList = Object.keys(object); - for(let idx = 0, length = objectKeyList.length; idx < length; idx += 1){ - inspectNode(object[objectKeyList[idx]], objects, duplicatesIndexes); - } - } - } - } -} -function getDuplicateReferences(object, state) { - const objects = [], duplicatesIndexes = []; - inspectNode(object, objects, duplicatesIndexes); - const length = duplicatesIndexes.length; - for(let index = 0; index < length; index += 1){ - state.duplicates.push(objects[duplicatesIndexes[index]]); - } - state.usedDuplicates = Array.from({ - length - }); -} -function dump(input, options) { - options = options || {}; - const state = new DumperState(options); - if (!state.noRefs) getDuplicateReferences(input, state); - if (writeNode(state, 0, input, true, true)) return `${state.dump}\n`; - return ""; -} -function stringify(obj, options) { - return dump(obj, options); -} -const mod1 = { - parse: parse, - parseAll: parseAll, - stringify: stringify, - Type: Type, - CORE_SCHEMA: core, - DEFAULT_SCHEMA: def, - EXTENDED_SCHEMA: extended, - FAILSAFE_SCHEMA: failsafe, - JSON_SCHEMA: json -}; -function incrementLastNumber(list) { - const newList = [ - ...list - ]; - newList[newList.length - 1]++; - return newList; -} -class EmVer { - values; - static from(range) { - if (range instanceof EmVer) { - return range; - } - return EmVer.parse(range); - } - static parse(range) { - const values = range.split(".").map((x)=>parseInt(x)); - for (const value of values){ - if (isNaN(value)) { - throw new Error(`Couldn't parse range: ${range}`); - } - } - return new EmVer(values); - } - constructor(values){ - this.values = values; - } - withLastIncremented() { - return new EmVer(incrementLastNumber(this.values)); - } - greaterThan(other) { - for(const i in this.values){ - if (other.values[i] == null) { - return true; - } - if (this.values[i] > other.values[i]) { - return true; - } - if (this.values[i] < other.values[i]) { - return false; - } - } - return false; - } - equals(other) { - if (other.values.length !== this.values.length) { - return false; - } - for(const i in this.values){ - if (this.values[i] !== other.values[i]) { - return false; - } - } - return true; - } - greaterThanOrEqual(other) { - return this.greaterThan(other) || this.equals(other); - } - lessThanOrEqual(other) { - return !this.greaterThan(other); - } - lessThan(other) { - return !this.greaterThanOrEqual(other); - } - compare(other) { - if (this.equals(other)) { - return "equal"; - } else if (this.greaterThan(other)) { - return "greater"; - } else { - return "less"; - } - } - compareForSort(other) { - return mod.matches(this.compare(other)).when("equal", ()=>0).when("greater", ()=>1).when("less", ()=>-1).unwrap(); - } -} -function migrationFn(fn) { - return fn; -} -function fromMapping(migrations, currentVersion) { - const directionShape = mod.literals("from", "to"); - return async (effects, version, direction)=>{ - if (!directionShape.test(direction)) { - return { - error: 'Must specify arg "from" or "to".' - }; - } - let configured = true; - const current = EmVer.parse(currentVersion); - const other = EmVer.parse(version); - const filteredMigrations = Object.entries(migrations).map(([version, migration])=>({ - version: EmVer.parse(version), - migration - })).filter(({ version })=>version.greaterThan(other) && version.lessThanOrEqual(current)); - const migrationsToRun = mod.matches(direction).when("from", ()=>filteredMigrations.sort((a, b)=>a.version.compareForSort(b.version)).map(({ migration })=>migration.up)).when("to", ()=>filteredMigrations.sort((a, b)=>b.version.compareForSort(a.version)).map(({ migration })=>migration.down)).unwrap(); - for (const migration of migrationsToRun){ - configured = (await migration(effects)).configured && configured; - } - return { - result: { - configured - } - }; - }; -} -function unwrapResultType(res) { - if ("error-code" in res) { - throw new Error(res["error-code"][1]); - } else if ("error" in res) { - throw new Error(res["error"]); - } else { - return res.result; - } -} -const exists = (effects, props)=>effects.metadata(props).then((_)=>true, (_)=>false); -const asResult = (result)=>({ - result: result - }); -const noPropertiesFound = { - result: { - version: 2, - data: { - "Not Ready": { - type: "string", - value: "Could not find properties. The service might still be starting", - qr: false, - copyable: false, - masked: false, - description: "Fallback Message When Properties could not be found" - } - } - } -}; -const properties = async (effects)=>{ - if (await exists(effects, { - path: "start9/stats.yaml", - volumeId: "main" - }) === false) { - return noPropertiesFound; - } - return await effects.readFile({ - path: "start9/stats.yaml", - volumeId: "main" - }).then(mod1.parse).then(asResult); -}; -const setConfig = async (effects, newConfig, dependsOn = {})=>{ - await effects.createDir({ - path: "start9", - volumeId: "main" - }); - await effects.writeFile({ - path: "start9/config.yaml", - toWrite: mod1.stringify(newConfig), - volumeId: "main" - }); - const result = { - signal: "SIGTERM", - "depends-on": dependsOn - }; - return { - result - }; -}; -const { any: any1, string: string1, dictionary: dictionary1 } = mod; -const matchConfig = dictionary1([ - string1, - any1 -]); -const getConfig = (spec)=>async (effects)=>{ - const config = await effects.readFile({ - path: "start9/config.yaml", - volumeId: "main" - }).then((x)=>mod1.parse(x)).then((x)=>matchConfig.unsafeCast(x)).catch((e)=>{ - effects.info(`Got error ${e} while trying to read the config`); - return undefined; - }); - return { - result: { - config, - spec - } - }; - }; -function updateConfig(fn, configured, noRepeat, noFail = false) { - return migrationFn(async (effects)=>{ - await noRepeatGuard(effects, noRepeat, async ()=>{ - let config = unwrapResultType(await getConfig({})(effects)).config; - if (config) { - try { - config = await fn(config, effects); - } catch (e) { - if (!noFail) { - throw e; - } else { - configured = false; - } - } - unwrapResultType(await setConfig(effects, config)); - } - }); - return { - configured - }; - }); -} -async function noRepeatGuard(effects, noRepeat, fn) { - if (!noRepeat) { - return fn(); - } - if (!await exists(effects, { - path: "start9/migrations", - volumeId: "main" - })) { - await effects.createDir({ - path: "start9/migrations", - volumeId: "main" - }); - } - const migrationPath = { - path: `start9/migrations/${noRepeat.version}.complete`, - volumeId: "main" - }; - if (noRepeat.type === "up") { - if (!await exists(effects, migrationPath)) { - await fn(); - await effects.writeFile({ - ...migrationPath, - toWrite: "" - }); - } - } else if (noRepeat.type === "down") { - if (await exists(effects, migrationPath)) { - await fn(); - await effects.removeFile(migrationPath); - } - } -} -async function initNoRepeat(effects, migrations, startingVersion) { - if (!await exists(effects, { - path: "start9/migrations", - volumeId: "main" - })) { - const starting = EmVer.parse(startingVersion); - await effects.createDir({ - path: "start9/migrations", - volumeId: "main" - }); - for(const version in migrations){ - const migrationVersion = EmVer.parse(version); - if (migrationVersion.lessThanOrEqual(starting)) { - await effects.writeFile({ - path: `start9/migrations/${version}.complete`, - volumeId: "main", - toWrite: "" - }); - } - } - } -} -function fromMapping1(migrations, currentVersion) { - const inner = fromMapping(migrations, currentVersion); - return async (effects, version, direction)=>{ - await initNoRepeat(effects, migrations, direction === "from" ? version : currentVersion); - return inner(effects, version, direction); - }; -} -const mod2 = { - updateConfig: updateConfig, - noRepeatGuard: noRepeatGuard, - initNoRepeat: initNoRepeat, - fromMapping: fromMapping1 -}; -const mod3 = { - properties: properties, - setConfig: setConfig, - getConfig: getConfig, - migrations: mod2 -}; -const setConfig1 = async (effects, input)=>{ - const newConfig = input; - const depsLnd = newConfig?.implementation === "LndRestWallet" ? { - lnd: [] - } : {}; - const depsCln = newConfig?.implementation === "CLightningWallet" ? { - "c-lightning": [] - } : {}; - return await mod3.setConfig(effects, input, { - ...depsLnd, - ...depsCln - }); -}; -const properties1 = mod3.properties; -const getConfig1 = mod3.getConfig({ - "tor-address": { - "name": "Tor Address", - "description": "The Tor address of the network interface", - "type": "pointer", - "subtype": "package", - "package-id": "lnbits", - "target": "tor-address", - "interface": "main" - }, - "lan-address": { - "name": "LAN Address", - "description": "The LAN address of the network interface", - "type": "pointer", - "subtype": "package", - "package-id": "lnbits", - "target": "lan-address", - "interface": "main" - }, - "implementation": { - "type": "enum", - "name": "Lightning Implementation", - "description": "The underlying Lightning implementation, currently LND or Core Lightning (CLN)", - "warning": "If you change the LN implementation after using LNBits this will delete all LNBits accounts and wallets related to the previously configured LN implementation! All LN funds will still be available on the underlying LN implementation.", - "values": [ - "LndRestWallet", - "CLightningWallet" - ], - 'value-names': { - "LndRestWallet": "LND", - "CLightningWallet": "Core Lightning" - }, - "default": "LndRestWallet" - } -}); -const migration = mod3.migrations.fromMapping({ - "0.9.7.2": { - up: mod3.migrations.updateConfig((config)=>{ - return { - implementation: config?.wallet.type || "LndRestWallet" - }; - }, true, { - version: "0.9.7.2", - type: "up" - }), - down: mod3.migrations.updateConfig((config)=>{ - return { - wallet: { - type: config.implementation || "LndRestWallet" - } - }; - }, true, { - version: "0.9.7.2", - type: "down" - }) - }, - "1.0.0": { - up: mod3.migrations.updateConfig((config)=>{ - return config; - }, true, { - version: "1.0.0", - type: "up" - }), - down: ()=>{ - throw new Error("Cannot downgrade"); - } - } -}, "1.1.0"); -export { setConfig1 as setConfig }; -export { properties1 as properties }; -export { getConfig1 as getConfig }; -export { migration as migration }; diff --git a/Old/scripts/embassy.ts b/Old/scripts/embassy.ts deleted file mode 100644 index 64814e0..0000000 --- a/Old/scripts/embassy.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { setConfig } from "./services/setConfig.ts"; -export { properties } from "./services/properties.ts"; -export { getConfig } from "./services/getConfig.ts"; -export { migration } from "./services/migrations.ts"; -// export { health } from "./services/healthChecks.ts"; diff --git a/Old/scripts/services/getConfig.ts b/Old/scripts/services/getConfig.ts deleted file mode 100644 index 43ddbb3..0000000 --- a/Old/scripts/services/getConfig.ts +++ /dev/null @@ -1,36 +0,0 @@ -// To utilize the default config system built, this file is required. It defines the *structure* of the configuration file. These structured options display as changeable UI elements within the "Config" section of the service details page in the StartOS UI. - -import { compat, types as T } from "../deps.ts"; - -export const getConfig: T.ExpectedExports.getConfig = compat.getConfig({ - "tor-address": { - "name": "Tor Address", - "description": "The Tor address of the network interface", - "type": "pointer", - "subtype": "package", - "package-id": "lnbits", - "target": "tor-address", - "interface": "main", - }, - "lan-address": { - "name": "LAN Address", - "description": "The LAN address of the network interface", - "type": "pointer", - "subtype": "package", - "package-id": "lnbits", - "target": "lan-address", - "interface": "main", - }, - "implementation": { - "type": "enum", - "name": "Lightning Implementation", - "description": "The underlying Lightning implementation, currently LND or Core Lightning (CLN)", - "warning": "If you change the LN implementation after using LNBits this will delete all LNBits accounts and wallets related to the previously configured LN implementation! All LN funds will still be available on the underlying LN implementation.", - "values": ["LndRestWallet", "CLightningWallet"], - 'value-names': { - "LndRestWallet": "LND", - "CLightningWallet": "Core Lightning", - }, - "default": "LndRestWallet", - } -}); diff --git a/Old/scripts/services/healthChecks.ts b/Old/scripts/services/healthChecks.ts deleted file mode 100644 index a9657cf..0000000 --- a/Old/scripts/services/healthChecks.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { types as T, healthUtil } from "../deps.ts"; - -export const health: T.ExpectedExports.health = { - "web-ui": healthUtil.checkWebUrl("http://lnbits.embassy:5000") -} diff --git a/Old/scripts/services/migrations.ts b/Old/scripts/services/migrations.ts deleted file mode 100644 index e1a4577..0000000 --- a/Old/scripts/services/migrations.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { compat, types as T } from "../deps.ts"; - -export const migration: T.ExpectedExports.migration = - compat.migrations.fromMapping( - { - "0.9.7.2": { - up: compat.migrations.updateConfig( - (config: any) => { - return { implementation: config?.wallet.type || "LndRestWallet" }; - }, - true, - { version: "0.9.7.2", type: "up" } - ), - down: compat.migrations.updateConfig( - (config: any) => { - return { - wallet: { type: config.implementation || "LndRestWallet" }, - }; - }, - true, - { version: "0.9.7.2", type: "down" } - ), - }, - "1.0.0": { - up: compat.migrations.updateConfig((config: any) => { - return config - }, true, { - version: "1.0.0", - type: "up", - }), - down: () => { - throw new Error("Cannot downgrade"); - }, - }, - }, - "1.1.0" - ); diff --git a/Old/scripts/services/properties.ts b/Old/scripts/services/properties.ts deleted file mode 100644 index dff99aa..0000000 --- a/Old/scripts/services/properties.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { compat, types as T } from "../deps.ts"; - -export const properties: T.ExpectedExports.properties = compat.properties; diff --git a/Old/scripts/services/setConfig.ts b/Old/scripts/services/setConfig.ts deleted file mode 100644 index 503628c..0000000 --- a/Old/scripts/services/setConfig.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { - compat, - types as T - } from "../deps.ts"; - export const setConfig: T.ExpectedExports.setConfig = async (effects, input ) => { - // deno-lint-ignore no-explicit-any - const newConfig = input as any; - - const depsLnd: T.DependsOn = newConfig?.implementation === "LndRestWallet" ? {lnd: []} : {} - const depsCln: T.DependsOn = newConfig?.implementation === "CLightningWallet" ? {"c-lightning": []} : {} - - return await compat.setConfig(effects,input, { - ...depsLnd, - ...depsCln, - }) - } \ No newline at end of file diff --git a/Old/workflows/buildService.yml b/Old/workflows/buildService.yml deleted file mode 100644 index de30e20..0000000 --- a/Old/workflows/buildService.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Build Service - -on: - workflow_dispatch: - pull_request: - paths-ignore: ['*.md'] - branches: ['main', 'master'] - push: - paths-ignore: ['*.md'] - branches: ['main', 'master'] - -jobs: - BuildPackage: - runs-on: ubuntu-latest - steps: - - name: Prepare StartOS SDK - uses: Start9Labs/sdk@v1 - - - name: Checkout services repository - uses: actions/checkout@v4 - - - name: Build the service package - id: build - run: | - git submodule update --init --recursive - start-sdk init - make - PACKAGE_ID=$(yq -oy ".id" manifest.*) - echo "package_id=$PACKAGE_ID" >> $GITHUB_ENV - shell: bash - - - name: Upload .s9pk - uses: actions/upload-artifact@v4 - with: - name: ${{ env.package_id }}.s9pk - path: ./${{ env.package_id }}.s9pk \ No newline at end of file diff --git a/Old/workflows/releaseService.yml b/Old/workflows/releaseService.yml deleted file mode 100644 index 125d8c2..0000000 --- a/Old/workflows/releaseService.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: Release Service - -on: - push: - tags: - - 'v*.*' - -jobs: - ReleasePackage: - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Prepare StartOS SDK - uses: Start9Labs/sdk@v1 - - - name: Checkout services repository - uses: actions/checkout@v4 - - - name: Build the service package - run: | - git submodule update --init --recursive - start-sdk init - make - - - name: Setting package ID and title from the manifest - id: package - run: | - echo "package_id=$(yq -oy ".id" manifest.*)" >> $GITHUB_ENV - echo "package_title=$(yq -oy ".title" manifest.*)" >> $GITHUB_ENV - shell: bash - - - name: Generate sha256 checksum - run: | - PACKAGE_ID=${{ env.package_id }} - sha256sum ${PACKAGE_ID}.s9pk > ${PACKAGE_ID}.s9pk.sha256 - shell: bash - - - name: Generate changelog - run: | - PACKAGE_ID=${{ env.package_id }} - echo "## What's Changed" > change-log.txt - yq -oy '.release-notes' manifest.* >> change-log.txt - echo "## SHA256 Hash" >> change-log.txt - echo '```' >> change-log.txt - sha256sum ${PACKAGE_ID}.s9pk >> change-log.txt - echo '```' >> change-log.txt - shell: bash - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ github.ref_name }} - name: ${{ env.package_title }} ${{ github.ref_name }} - prerelease: true - body_path: change-log.txt - files: | - ./${{ env.package_id }}.s9pk - ./${{ env.package_id }}.s9pk.sha256 - - - name: Publish to Registry - env: - S9USER: ${{ secrets.S9USER }} - S9PASS: ${{ secrets.S9PASS }} - S9REGISTRY: ${{ secrets.S9REGISTRY }} - run: | - if [[ -z "$S9USER" || -z "$S9PASS" || -z "$S9REGISTRY" ]]; then - echo "Publish skipped: missing registry credentials." - else - start-sdk publish https://$S9USER:$S9PASS@$S9REGISTRY ${{ env.package_id }}.s9pk - fi \ No newline at end of file From c955cede0491f3c8a2509026d53f62606d63d633 Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Fri, 31 Oct 2025 11:59:03 -0600 Subject: [PATCH 18/49] backwards compatible multihost id --- startos/interfaces.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/startos/interfaces.ts b/startos/interfaces.ts index 392428a..8b061a0 100644 --- a/startos/interfaces.ts +++ b/startos/interfaces.ts @@ -2,7 +2,7 @@ import { sdk } from './sdk' import { uiPort } from './utils' export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => { - const uiMulti = sdk.MultiHost.of(effects, 'ui-multi') + const uiMulti = sdk.MultiHost.of(effects, 'main') const uiMultiOrigin = await uiMulti.bindPort(uiPort, { protocol: 'http', }) From 8311c43ea155cff6bae18d546c51c380ea6df4a4 Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Tue, 4 Nov 2025 14:12:40 -0700 Subject: [PATCH 19/49] beta.42 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 49a921e..e736064 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "hello-world-startos", "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.41", + "@start9labs/start-sdk": "^0.4.0-beta.42", "js-yaml": "^4.1.0" }, "devDependencies": { @@ -51,9 +51,9 @@ } }, "node_modules/@start9labs/start-sdk": { - "version": "0.4.0-beta.41", - "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.41.tgz", - "integrity": "sha512-nbr6fT8qtAr04lkFBMnUARAAVrTYdjoXA61j/1IzIvA9vkrkras985SE3YSAkgQPYfaAI27wLHQcAn8U0Kk01g==", + "version": "0.4.0-beta.42", + "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.42.tgz", + "integrity": "sha512-satQEsdWXbZCJVhCoZxjJoi1bReuB+IzLUVJrtM9lRdiGEmz5LafEYkua0/IhrPBXIT7TFt5wL6D6Yl09N2uVQ==", "license": "MIT", "dependencies": { "@iarna/toml": "^3.0.0", diff --git a/package.json b/package.json index eac0029..2699bcc 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "check": "tsc --noEmit" }, "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.41", + "@start9labs/start-sdk": "^0.4.0-beta.42", "js-yaml": "^4.1.0" }, "devDependencies": { From 6065d01a77c6f98be28582fbc42e5de6c70b6279 Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Tue, 4 Nov 2025 14:16:44 -0700 Subject: [PATCH 20/49] bump pre-release tag --- startos/install/versions/index.ts | 2 +- .../versions/{v1_3_1_1-alpha.0.ts => v1_3_1_1-alpha.1.ts} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename startos/install/versions/{v1_3_1_1-alpha.0.ts => v1_3_1_1-alpha.1.ts} (66%) diff --git a/startos/install/versions/index.ts b/startos/install/versions/index.ts index 4292c1f..7082a0c 100644 --- a/startos/install/versions/index.ts +++ b/startos/install/versions/index.ts @@ -1,4 +1,4 @@ import { v1_2_1_1 } from './v1_2_1_1' -export { v1_3_1_1_alpha0 as current } from './v1_3_1_1-alpha.0' +export { v1_3_1_1_alpha1 as current } from './v1_3_1_1-alpha.1' export const other = [v1_2_1_1] diff --git a/startos/install/versions/v1_3_1_1-alpha.0.ts b/startos/install/versions/v1_3_1_1-alpha.1.ts similarity index 66% rename from startos/install/versions/v1_3_1_1-alpha.0.ts rename to startos/install/versions/v1_3_1_1-alpha.1.ts index 67c81d7..237ea0a 100644 --- a/startos/install/versions/v1_3_1_1-alpha.0.ts +++ b/startos/install/versions/v1_3_1_1-alpha.1.ts @@ -1,7 +1,7 @@ import { VersionInfo, IMPOSSIBLE } from '@start9labs/start-sdk' -export const v1_3_1_1_alpha0 = VersionInfo.of({ - version: '1.3.1:1-alpha.0', +export const v1_3_1_1_alpha1 = VersionInfo.of({ + version: '1.3.1:1-alpha.1', releaseNotes: 'Revamped for StartOS 0.4.0', migrations: { down: IMPOSSIBLE, From 602bfd30dabb9f1c4805c9f31c4949a80fff91a3 Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Tue, 4 Nov 2025 14:19:04 -0700 Subject: [PATCH 21/49] bump manifest deps --- startos/manifest.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/startos/manifest.ts b/startos/manifest.ts index 3fc6c15..e90fa3c 100644 --- a/startos/manifest.ts +++ b/startos/manifest.ts @@ -46,12 +46,12 @@ export const manifest = setupManifest({ 'c-lightning': { description: 'Optionally connect RTL to your CLN node.', optional: true, - s9pk: 'https://github.com/Start9Labs/cln-startos/releases/download/v25.09.0.1-beta.0/c-lightning.s9pk', + s9pk: 'https://github.com/Start9Labs/cln-startos/releases/download/v25.09.1.2-beta.1/c-lightning.s9pk', }, lnd: { description: 'Optionally connect RTL to your LND node.', optional: true, - s9pk: 'https://github.com/Start9Labs/lnd-startos/releases/download/v0.19.3-beta.1-beta.0/lnd.s9pk', + s9pk: 'https://github.com/Start9Labs/lnd-startos/releases/download/v0.19.3-beta.1-beta.2/lnd.s9pk', }, }, }) From 8fd8f1600020343cf84a65e11bbb50f497530b5c Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Wed, 19 Nov 2025 22:22:18 -0700 Subject: [PATCH 22/49] beta.44 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index e736064..498d0a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "hello-world-startos", "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.42", + "@start9labs/start-sdk": "^0.4.0-beta.44", "js-yaml": "^4.1.0" }, "devDependencies": { @@ -51,9 +51,9 @@ } }, "node_modules/@start9labs/start-sdk": { - "version": "0.4.0-beta.42", - "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.42.tgz", - "integrity": "sha512-satQEsdWXbZCJVhCoZxjJoi1bReuB+IzLUVJrtM9lRdiGEmz5LafEYkua0/IhrPBXIT7TFt5wL6D6Yl09N2uVQ==", + "version": "0.4.0-beta.44", + "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.44.tgz", + "integrity": "sha512-7/4YaEqMnaHB6A04HraCtrZNcMWaRwBDE2Lt8y+V7WvkxKt2aQ7wJvgdq4gxwoFFcNDwq1CZSfXPzd7rIc3wQg==", "license": "MIT", "dependencies": { "@iarna/toml": "^3.0.0", diff --git a/package.json b/package.json index 2699bcc..15cf906 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "check": "tsc --noEmit" }, "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.42", + "@start9labs/start-sdk": "^0.4.0-beta.44", "js-yaml": "^4.1.0" }, "devDependencies": { From 006932f0ccd5d2b999e477ca83c4ebc1bc1df9ec Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Wed, 19 Nov 2025 22:22:25 -0700 Subject: [PATCH 23/49] latest CI --- .github/workflows/buildService.yml | 6 +++--- .github/workflows/releaseService.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/buildService.yml b/.github/workflows/buildService.yml index 06432ce..c5127f6 100644 --- a/.github/workflows/buildService.yml +++ b/.github/workflows/buildService.yml @@ -17,14 +17,14 @@ jobs: uses: start9labs/sdk@v2 - name: Checkout services repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive - name: Build the service package id: build run: | - start-cli init + start-cli init-key RUST_LOG=debug RUST_BACKTRACE=1 make PACKAGE_ID=$(start-cli s9pk inspect *.s9pk manifest | jq -r '.id') echo "package_id=${PACKAGE_ID}" >> $GITHUB_ENV @@ -32,7 +32,7 @@ jobs: shell: bash - name: Upload .s9pk - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: ${{ env.package_id }}.s9pk path: ./${{ env.package_id }}.s9pk diff --git a/.github/workflows/releaseService.yml b/.github/workflows/releaseService.yml index 99fa697..f55f1b3 100644 --- a/.github/workflows/releaseService.yml +++ b/.github/workflows/releaseService.yml @@ -15,7 +15,7 @@ jobs: uses: start9labs/sdk@v2 - name: Checkout services repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive @@ -24,7 +24,7 @@ jobs: env: S9DEVKEY: ${{ secrets.S9DEVKEY }} run: | - start-cli init + start-cli init-key if [[ -n "$S9DEVKEY" ]]; then echo "Using developer key from secrets to sign the package." printf '%s' "$S9DEVKEY" > ~/.startos/developer.key.pem From 3a240483e7d34ce181ff30796fd2bd30d7fdc834 Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Wed, 19 Nov 2025 22:22:50 -0700 Subject: [PATCH 24/49] latest makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3a64f2b..9105a00 100644 --- a/Makefile +++ b/Makefile @@ -71,7 +71,7 @@ check-deps: check-init: @if [ ! -f ~/.startos/developer.key.pem ]; then \ echo "Initializing StartOS developer environment..."; \ - start-cli init; \ + start-cli init-key; \ fi javascript/index.js: $(shell find startos -type f) tsconfig.json node_modules From 6828d011e04b043585f09d532954750d50de84bd Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Wed, 19 Nov 2025 22:24:20 -0700 Subject: [PATCH 25/49] bump pre-release --- startos/install/versions/index.ts | 2 +- .../versions/{v1_3_1_1-alpha.1.ts => v1_3_1_1-beta.0.ts} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename startos/install/versions/{v1_3_1_1-alpha.1.ts => v1_3_1_1-beta.0.ts} (66%) diff --git a/startos/install/versions/index.ts b/startos/install/versions/index.ts index 7082a0c..314eeac 100644 --- a/startos/install/versions/index.ts +++ b/startos/install/versions/index.ts @@ -1,4 +1,4 @@ import { v1_2_1_1 } from './v1_2_1_1' -export { v1_3_1_1_alpha1 as current } from './v1_3_1_1-alpha.1' +export { v1_3_1_1_beta0 as current } from './v1_3_1_1-beta.0' export const other = [v1_2_1_1] diff --git a/startos/install/versions/v1_3_1_1-alpha.1.ts b/startos/install/versions/v1_3_1_1-beta.0.ts similarity index 66% rename from startos/install/versions/v1_3_1_1-alpha.1.ts rename to startos/install/versions/v1_3_1_1-beta.0.ts index 237ea0a..570fdec 100644 --- a/startos/install/versions/v1_3_1_1-alpha.1.ts +++ b/startos/install/versions/v1_3_1_1-beta.0.ts @@ -1,7 +1,7 @@ import { VersionInfo, IMPOSSIBLE } from '@start9labs/start-sdk' -export const v1_3_1_1_alpha1 = VersionInfo.of({ - version: '1.3.1:1-alpha.1', +export const v1_3_1_1_beta0 = VersionInfo.of({ + version: '1.3.1:1-beta.0', releaseNotes: 'Revamped for StartOS 0.4.0', migrations: { down: IMPOSSIBLE, From 7110e60a56fabf1a0319cce7a32ef665af69acd1 Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Mon, 1 Dec 2025 12:15:58 -0700 Subject: [PATCH 26/49] use latest dep s9pks --- startos/manifest.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/startos/manifest.ts b/startos/manifest.ts index e90fa3c..b9f0590 100644 --- a/startos/manifest.ts +++ b/startos/manifest.ts @@ -46,12 +46,12 @@ export const manifest = setupManifest({ 'c-lightning': { description: 'Optionally connect RTL to your CLN node.', optional: true, - s9pk: 'https://github.com/Start9Labs/cln-startos/releases/download/v25.09.1.2-beta.1/c-lightning.s9pk', + s9pk: 'https://github.com/Start9Labs/cln-startos/releases/download/v25.09.3.1-beta.0/c-lightning.s9pk', }, lnd: { description: 'Optionally connect RTL to your LND node.', optional: true, - s9pk: 'https://github.com/Start9Labs/lnd-startos/releases/download/v0.19.3-beta.1-beta.2/lnd.s9pk', + s9pk: 'https://github.com/Start9Labs/lnd-startos/releases/download/v0.20.0-beta.1-beta.0/lnd.s9pk', }, }, }) From 6ef934386e440e3e19fe9274017bc78e57bc567d Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Mon, 1 Dec 2025 12:33:25 -0700 Subject: [PATCH 27/49] this isn't hello-world --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 498d0a6..e3d1d39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,10 +1,10 @@ { - "name": "hello-world-startos", + "name": "lnbits-startos", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "hello-world-startos", + "name": "lnbits-startos", "dependencies": { "@start9labs/start-sdk": "^0.4.0-beta.44", "js-yaml": "^4.1.0" diff --git a/package.json b/package.json index 15cf906..54e11e1 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "hello-world-startos", + "name": "lnbits-startos", "scripts": { "build": "rm -rf ./javascript && ncc build startos/index.ts -o ./javascript", "prettier": "prettier --write startos", From acc31bd14f6aca360b0123c1ea817a7b1776cc60 Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Mon, 1 Dec 2025 12:39:59 -0700 Subject: [PATCH 28/49] uv instead of poetry --- startos/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/startos/main.ts b/startos/main.ts index 0163ee1..cc43a1b 100644 --- a/startos/main.ts +++ b/startos/main.ts @@ -42,7 +42,7 @@ export const main = sdk.setupMain(async ({ effects, started }) => { */ return sdk.Daemons.of(effects, started).addDaemon('primary', { subcontainer: lnbitsSub, - exec: { command: ['poetry', 'run', 'lnbits'], env: env || {} }, + exec: { command: ['uv', 'run', 'lnbits'], env: env || {} }, ready: { display: 'Web Interface', gracePeriod: 75_000, From 72e7d596df5e5e6f9c3578744dac7ee49f1eb195 Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Mon, 1 Dec 2025 12:49:27 -0700 Subject: [PATCH 29/49] bump pre-release --- startos/install/versions/index.ts | 2 +- .../versions/{v1_3_1_1-beta.0.ts => v1_3_1_1-beta.1.ts} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename startos/install/versions/{v1_3_1_1-beta.0.ts => v1_3_1_1-beta.1.ts} (67%) diff --git a/startos/install/versions/index.ts b/startos/install/versions/index.ts index 314eeac..fade9af 100644 --- a/startos/install/versions/index.ts +++ b/startos/install/versions/index.ts @@ -1,4 +1,4 @@ import { v1_2_1_1 } from './v1_2_1_1' -export { v1_3_1_1_beta0 as current } from './v1_3_1_1-beta.0' +export { v1_3_1_1_beta1 as current } from './v1_3_1_1-beta.1' export const other = [v1_2_1_1] diff --git a/startos/install/versions/v1_3_1_1-beta.0.ts b/startos/install/versions/v1_3_1_1-beta.1.ts similarity index 67% rename from startos/install/versions/v1_3_1_1-beta.0.ts rename to startos/install/versions/v1_3_1_1-beta.1.ts index 570fdec..c10e77f 100644 --- a/startos/install/versions/v1_3_1_1-beta.0.ts +++ b/startos/install/versions/v1_3_1_1-beta.1.ts @@ -1,7 +1,7 @@ import { VersionInfo, IMPOSSIBLE } from '@start9labs/start-sdk' -export const v1_3_1_1_beta0 = VersionInfo.of({ - version: '1.3.1:1-beta.0', +export const v1_3_1_1_beta1 = VersionInfo.of({ + version: '1.3.1:1-beta.1', releaseNotes: 'Revamped for StartOS 0.4.0', migrations: { down: IMPOSSIBLE, From c40edee2a3ee6f85752394b3a2c1b12aae0572a0 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Mon, 1 Dec 2025 17:59:50 -0700 Subject: [PATCH 30/49] fix broken CLN dependency and dont show dep until selected --- startos/actions/lightningImplementation.ts | 8 ++++---- startos/dependencies.ts | 2 +- startos/fileModels/env.ts | 1 + startos/install/versionGraph.ts | 9 ++++----- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/startos/actions/lightningImplementation.ts b/startos/actions/lightningImplementation.ts index 60eb8e7..79edd74 100644 --- a/startos/actions/lightningImplementation.ts +++ b/startos/actions/lightningImplementation.ts @@ -13,7 +13,7 @@ export const inputSpec = InputSpec.of({ LndRestWallet: 'LND', CoreLightningWallet: 'Core Lightning', }, - default: 'LndRestWallet', + default: undefined as any, }), }) @@ -37,14 +37,14 @@ export const setLnImplementation = sdk.Action.withInput( // optionally pre-fill the input form async ({ effects }) => { - const configuredLnImplementation = await envFile + const imp = await envFile .read((e) => e.LNBITS_BACKEND_WALLET_CLASS) .const(effects) - if (!configuredLnImplementation) return + if (!imp || imp === 'VoidWallet') return return { - implementation: configuredLnImplementation, + implementation: imp, } }, diff --git a/startos/dependencies.ts b/startos/dependencies.ts index 3b0d093..ac92460 100644 --- a/startos/dependencies.ts +++ b/startos/dependencies.ts @@ -19,7 +19,7 @@ export const setDependencies = sdk.setupDependencies(async ({ effects }) => { 'c-lightning': { healthChecks: ['lightningd'], kind: 'running', - versionRange: '>=v25.09.0.1-beta.0', + versionRange: '>=25.9.3:1-beta.1', }, } } diff --git a/startos/fileModels/env.ts b/startos/fileModels/env.ts index d9c9d80..22a1871 100644 --- a/startos/fileModels/env.ts +++ b/startos/fileModels/env.ts @@ -107,6 +107,7 @@ const shape = object({ // Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet, LnTipsWallet // LndRestWallet, CoreLightningWallet, LNbitsWallet, SparkWallet, FakeWallet, EclairWallet LNBITS_BACKEND_WALLET_CLASS: literals( + 'VoidWallet', 'LndRestWallet', 'CoreLightningWallet', ).onMismatch(LNBITS_BACKEND_WALLET_CLASS), diff --git a/startos/install/versionGraph.ts b/startos/install/versionGraph.ts index 2825e57..e87288c 100644 --- a/startos/install/versionGraph.ts +++ b/startos/install/versionGraph.ts @@ -7,10 +7,9 @@ export const versionGraph = VersionGraph.of({ current, other, preInstall: async (effects) => { - const existingEnv = await envFile.read().once() - - if (!existingEnv) { - await envFile.write(effects, envDefaults) - } + await envFile.write(effects, { + ...envDefaults, + LNBITS_BACKEND_WALLET_CLASS: 'VoidWallet', + }) }, }) From 159ad1cef193f573dd86b5a6a46ea345ac2c27c7 Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Tue, 2 Dec 2025 10:24:52 -0700 Subject: [PATCH 31/49] bump pre-release --- startos/install/versions/index.ts | 2 +- .../versions/{v1_3_1_1-beta.1.ts => v1_3_1_1-beta.2.ts} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename startos/install/versions/{v1_3_1_1-beta.1.ts => v1_3_1_1-beta.2.ts} (67%) diff --git a/startos/install/versions/index.ts b/startos/install/versions/index.ts index fade9af..14c7258 100644 --- a/startos/install/versions/index.ts +++ b/startos/install/versions/index.ts @@ -1,4 +1,4 @@ import { v1_2_1_1 } from './v1_2_1_1' -export { v1_3_1_1_beta1 as current } from './v1_3_1_1-beta.1' +export { v1_3_1_1_beta2 as current } from './v1_3_1_1-beta.2' export const other = [v1_2_1_1] diff --git a/startos/install/versions/v1_3_1_1-beta.1.ts b/startos/install/versions/v1_3_1_1-beta.2.ts similarity index 67% rename from startos/install/versions/v1_3_1_1-beta.1.ts rename to startos/install/versions/v1_3_1_1-beta.2.ts index c10e77f..8903c92 100644 --- a/startos/install/versions/v1_3_1_1-beta.1.ts +++ b/startos/install/versions/v1_3_1_1-beta.2.ts @@ -1,7 +1,7 @@ import { VersionInfo, IMPOSSIBLE } from '@start9labs/start-sdk' -export const v1_3_1_1_beta1 = VersionInfo.of({ - version: '1.3.1:1-beta.1', +export const v1_3_1_1_beta2 = VersionInfo.of({ + version: '1.3.1:1-beta.2', releaseNotes: 'Revamped for StartOS 0.4.0', migrations: { down: IMPOSSIBLE, From eee46f48712649432ef8abe6760b0d255b665873 Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Wed, 17 Dec 2025 17:51:01 -0700 Subject: [PATCH 32/49] beta.45 and npm update --- package-lock.json | 41 ++++++++++++++++++++++------------------- package.json | 2 +- startos/main.ts | 4 ++-- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index e3d1d39..775cdc6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "lnbits-startos", "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.44", + "@start9labs/start-sdk": "^0.4.0-beta.45", "js-yaml": "^4.1.0" }, "devDependencies": { @@ -51,9 +51,9 @@ } }, "node_modules/@start9labs/start-sdk": { - "version": "0.4.0-beta.44", - "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.44.tgz", - "integrity": "sha512-7/4YaEqMnaHB6A04HraCtrZNcMWaRwBDE2Lt8y+V7WvkxKt2aQ7wJvgdq4gxwoFFcNDwq1CZSfXPzd7rIc3wQg==", + "version": "0.4.0-beta.45", + "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.45.tgz", + "integrity": "sha512-N4iIifr5f1ElB4zcZnW8hCkPOAsfCxf+1UGqp249Tzm+gIRj45aq1wiE+vGCeSyGIuHsfMp3h5Pouyvfn33FOQ==", "license": "MIT", "dependencies": { "@iarna/toml": "^3.0.0", @@ -82,9 +82,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.18.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.6.tgz", - "integrity": "sha512-r8uszLPpeIWbNKtvWRt/DbVi5zbqZyj1PTmhRMqBMvDnaz1QpmSKujUtJLrqGZeoM8v72MfYggDceY4K1itzWQ==", + "version": "22.19.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", + "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", "dev": true, "license": "MIT", "dependencies": { @@ -136,9 +136,9 @@ } }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -192,9 +192,9 @@ } }, "node_modules/prettier": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", + "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, "license": "MIT", "bin": { @@ -220,9 +220,9 @@ "license": "MIT" }, "node_modules/typescript": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "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": { @@ -263,15 +263,18 @@ } }, "node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "license": "ISC", "bin": { "yaml": "bin.mjs" }, "engines": { "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" } } } diff --git a/package.json b/package.json index 54e11e1..70109cc 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "check": "tsc --noEmit" }, "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.44", + "@start9labs/start-sdk": "^0.4.0-beta.45", "js-yaml": "^4.1.0" }, "devDependencies": { diff --git a/startos/main.ts b/startos/main.ts index cc43a1b..dd85453 100644 --- a/startos/main.ts +++ b/startos/main.ts @@ -2,7 +2,7 @@ import { envFile } from './fileModels/env' import { sdk } from './sdk' import { mainMounts, uiPort } from './utils' -export const main = sdk.setupMain(async ({ effects, started }) => { +export const main = sdk.setupMain(async ({ effects }) => { /** * ======================== Setup (optional) ======================== * @@ -40,7 +40,7 @@ export const main = sdk.setupMain(async ({ effects, started }) => { * * Each daemon defines its own health check, which can optionally be exposed to the user. */ - return sdk.Daemons.of(effects, started).addDaemon('primary', { + return sdk.Daemons.of(effects).addDaemon('primary', { subcontainer: lnbitsSub, exec: { command: ['uv', 'run', 'lnbits'], env: env || {} }, ready: { From e7faaf26bd1dc03e58c7b60c52ef6758692f269d Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Wed, 17 Dec 2025 17:52:49 -0700 Subject: [PATCH 33/49] update dep s9pk links --- startos/manifest.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/startos/manifest.ts b/startos/manifest.ts index b9f0590..59ca5da 100644 --- a/startos/manifest.ts +++ b/startos/manifest.ts @@ -46,12 +46,12 @@ export const manifest = setupManifest({ 'c-lightning': { description: 'Optionally connect RTL to your CLN node.', optional: true, - s9pk: 'https://github.com/Start9Labs/cln-startos/releases/download/v25.09.3.1-beta.0/c-lightning.s9pk', + s9pk: 'https://github.com/Start9Labs/cln-startos/releases/download/v25.12.0.1-beta.0/c-lightning.s9pk', }, lnd: { description: 'Optionally connect RTL to your LND node.', optional: true, - s9pk: 'https://github.com/Start9Labs/lnd-startos/releases/download/v0.20.0-beta.1-beta.0/lnd.s9pk', + s9pk: 'https://github.com/Start9Labs/lnd-startos/releases/download/v0.20.0-beta.1-beta.1/lnd.s9pk', }, }, }) From 051c216b08f10ddf01a8a624f9c92c6e668160bb Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Wed, 17 Dec 2025 17:55:18 -0700 Subject: [PATCH 34/49] version cleanup --- startos/install/versions/index.ts | 5 ++--- .../versions/{v1_2_1_1.ts => v1.3.1_1-beta.0.ts} | 10 +++++++--- startos/install/versions/v1_3_1_1-beta.2.ts | 9 --------- 3 files changed, 9 insertions(+), 15 deletions(-) rename startos/install/versions/{v1_2_1_1.ts => v1.3.1_1-beta.0.ts} (83%) delete mode 100644 startos/install/versions/v1_3_1_1-beta.2.ts diff --git a/startos/install/versions/index.ts b/startos/install/versions/index.ts index 14c7258..d968464 100644 --- a/startos/install/versions/index.ts +++ b/startos/install/versions/index.ts @@ -1,4 +1,3 @@ -import { v1_2_1_1 } from './v1_2_1_1' -export { v1_3_1_1_beta2 as current } from './v1_3_1_1-beta.2' +export { v1_3_1_1 as current } from './v1.3.1_1-beta.0' -export const other = [v1_2_1_1] +export const other = [] diff --git a/startos/install/versions/v1_2_1_1.ts b/startos/install/versions/v1.3.1_1-beta.0.ts similarity index 83% rename from startos/install/versions/v1_2_1_1.ts rename to startos/install/versions/v1.3.1_1-beta.0.ts index bbc0b11..3717b44 100644 --- a/startos/install/versions/v1_2_1_1.ts +++ b/startos/install/versions/v1.3.1_1-beta.0.ts @@ -2,10 +2,10 @@ import { VersionInfo, IMPOSSIBLE } from '@start9labs/start-sdk' import { envFile } from '../../fileModels/env' import { envDefaults } from '../../utils' import { load } from 'js-yaml' -import { readFile } from 'fs/promises' +import { readFile, rm } from 'fs/promises' -export const v1_2_1_1 = VersionInfo.of({ - version: '1.2.1:1-alpha.0', +export const v1_3_1_1 = VersionInfo.of({ + version: '1.2.1:1-beta.0', releaseNotes: 'Revamped for StartOS 0.4.0', migrations: { up: async ({ effects }) => { @@ -29,6 +29,10 @@ export const v1_2_1_1 = VersionInfo.of({ LNBITS_BACKEND_WALLET_CLASS: configuredImplementation, LNBITS_ALLOWED_FUNDING_SOURCES: configuredImplementation, }) + + rm('/media/startos/volumes/main/start9', { + recursive: true, + }).catch(console.error) } catch { console.log('config.yaml not found') } diff --git a/startos/install/versions/v1_3_1_1-beta.2.ts b/startos/install/versions/v1_3_1_1-beta.2.ts deleted file mode 100644 index 8903c92..0000000 --- a/startos/install/versions/v1_3_1_1-beta.2.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { VersionInfo, IMPOSSIBLE } from '@start9labs/start-sdk' - -export const v1_3_1_1_beta2 = VersionInfo.of({ - version: '1.3.1:1-beta.2', - releaseNotes: 'Revamped for StartOS 0.4.0', - migrations: { - down: IMPOSSIBLE, - }, -}) From 30e296a078828de138cf138542664ab91d7fbd86 Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Wed, 17 Dec 2025 17:57:16 -0700 Subject: [PATCH 35/49] wrong pre-release sir --- startos/install/versions/index.ts | 2 +- .../install/versions/{v1.3.1_1-beta.0.ts => v1.3.1_1-beta.3.ts} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename startos/install/versions/{v1.3.1_1-beta.0.ts => v1.3.1_1-beta.3.ts} (97%) diff --git a/startos/install/versions/index.ts b/startos/install/versions/index.ts index d968464..6ed7145 100644 --- a/startos/install/versions/index.ts +++ b/startos/install/versions/index.ts @@ -1,3 +1,3 @@ -export { v1_3_1_1 as current } from './v1.3.1_1-beta.0' +export { v1_3_1_1 as current } from './v1.3.1_1-beta.3' export const other = [] diff --git a/startos/install/versions/v1.3.1_1-beta.0.ts b/startos/install/versions/v1.3.1_1-beta.3.ts similarity index 97% rename from startos/install/versions/v1.3.1_1-beta.0.ts rename to startos/install/versions/v1.3.1_1-beta.3.ts index 3717b44..80efe1c 100644 --- a/startos/install/versions/v1.3.1_1-beta.0.ts +++ b/startos/install/versions/v1.3.1_1-beta.3.ts @@ -5,7 +5,7 @@ import { load } from 'js-yaml' import { readFile, rm } from 'fs/promises' export const v1_3_1_1 = VersionInfo.of({ - version: '1.2.1:1-beta.0', + version: '1.2.1:1-beta.3', releaseNotes: 'Revamped for StartOS 0.4.0', migrations: { up: async ({ effects }) => { From 729c578f15e97cccef78395d6a551b3e7255923a Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Wed, 17 Dec 2025 18:00:23 -0700 Subject: [PATCH 36/49] Latest CI and Makefile --- .github/workflows/buildService.yml | 2 +- Makefile | 50 +++++++++++++++--------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/.github/workflows/buildService.yml b/.github/workflows/buildService.yml index c5127f6..fdac36a 100644 --- a/.github/workflows/buildService.yml +++ b/.github/workflows/buildService.yml @@ -35,4 +35,4 @@ jobs: uses: actions/upload-artifact@v5 with: name: ${{ env.package_id }}.s9pk - path: ./${{ env.package_id }}.s9pk + path: ./${{ env.package_id }}.s9pk \ No newline at end of file diff --git a/Makefile b/Makefile index 9105a00..fce5dc8 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,9 @@ PACKAGE_ID := $(shell awk -F"'" '/id:/ {print $$2}' startos/manifest.ts) INGREDIENTS := $(shell start-cli s9pk list-ingredients 2>/dev/null) -CMD_ARCH_GOAL := $(filter aarch64 x86_64 arm x86, $(MAKECMDGOALS)) -ifeq ($(CMD_ARCH_GOAL),) - BUILD := universal - S9PK := $(PACKAGE_ID).s9pk -else - RAW_ARCH := $(firstword $(CMD_ARCH_GOAL)) - ACTUAL_ARCH := $(patsubst x86,x86_64,$(patsubst arm,aarch64,$(RAW_ARCH))) - BUILD := $(ACTUAL_ARCH) - S9PK := $(PACKAGE_ID)_$(BUILD).s9pk -endif - -.PHONY: all aarch64 x86_64 arm x86 clean install check-deps check-init package ingredients +.PHONY: all 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); \ @@ -37,30 +27,41 @@ define SUMMARY endef all: $(PACKAGE_ID).s9pk - $(call SUMMARY,$(S9PK)) + $(call SUMMARY,$<) + +arch/%: $(PACKAGE_ID)_%.s9pk + $(call SUMMARY,$<) -$(BUILD): $(PACKAGE_ID)_$(BUILD).s9pk - $(call SUMMARY,$(S9PK)) +x86 x86_64: arch/x86_64 +arm arm64 aarch64: arch/aarch64 +riscv riscv64: arch/riscv64 -x86: x86_64 -arm: aarch64 +$(PACKAGE_ID).s9pk: $(INGREDIENTS) .git/HEAD .git/index + @$(MAKE) --no-print-directory ingredients + @echo " Packing '$@'..." + start-cli s9pk pack -o $@ -$(S9PK): $(INGREDIENTS) .git/HEAD .git/index +$(PACKAGE_ID)_%.s9pk: $(INGREDIENTS) .git/HEAD .git/index @$(MAKE) --no-print-directory ingredients - @echo " Packing '$(S9PK)'..." - BUILD=$(BUILD) start-cli s9pk pack -o $(S9PK) + @echo " Packing '$@'..." + start-cli s9pk pack --arch=$* -o $@ ingredients: $(INGREDIENTS) @echo " Re-evaluating ingredients..." -install: package | check-deps check-init +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; \ - echo "\n🚀 Installing to $$HOST ..."; \ - start-cli package install -s $(S9PK) + 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" check-deps: @command -v start-cli >/dev/null || \ @@ -84,5 +85,4 @@ package-lock.json: package.json npm i clean: - @echo "Cleaning up build artifacts..." - @rm -rf $(PACKAGE_ID).s9pk $(PACKAGE_ID)_x86_64.s9pk $(PACKAGE_ID)_aarch64.s9pk javascript node_modules \ No newline at end of file + @echo "Cleaning up build artifacts..." \ No newline at end of file From fc6310ea34e326e4e5dc04435e4d209585d6c42f Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Wed, 17 Dec 2025 18:02:23 -0700 Subject: [PATCH 37/49] actually 1.3.1 upstream --- startos/install/versions/v1.3.1_1-beta.3.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/startos/install/versions/v1.3.1_1-beta.3.ts b/startos/install/versions/v1.3.1_1-beta.3.ts index 80efe1c..98744f4 100644 --- a/startos/install/versions/v1.3.1_1-beta.3.ts +++ b/startos/install/versions/v1.3.1_1-beta.3.ts @@ -5,7 +5,7 @@ import { load } from 'js-yaml' import { readFile, rm } from 'fs/promises' export const v1_3_1_1 = VersionInfo.of({ - version: '1.2.1:1-beta.3', + version: '1.3.1:1-beta.3', releaseNotes: 'Revamped for StartOS 0.4.0', migrations: { up: async ({ effects }) => { From 01d3fd8fc4870030f75d615d5eea508c3becaf90 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Sun, 21 Dec 2025 17:16:48 -0700 Subject: [PATCH 38/49] workflows and shore up migration --- .github/workflows/buildService.yml | 30 ++------ .github/workflows/releaseService.yml | 79 ++------------------- startos/install/versions/v1.3.1_1-beta.3.ts | 32 ++++----- startos/manifest.ts | 23 +----- 4 files changed, 27 insertions(+), 137 deletions(-) diff --git a/.github/workflows/buildService.yml b/.github/workflows/buildService.yml index fdac36a..5d19b77 100644 --- a/.github/workflows/buildService.yml +++ b/.github/workflows/buildService.yml @@ -10,29 +10,7 @@ on: branches: ['main', 'master', 'update/040'] jobs: - BuildPackage: - runs-on: ubuntu-latest - steps: - - name: Prepare StartOS SDK - uses: start9labs/sdk@v2 - - - name: Checkout services repository - uses: actions/checkout@v5 - with: - submodules: recursive - - - name: Build the service package - id: build - run: | - start-cli init-key - RUST_LOG=debug RUST_BACKTRACE=1 make - PACKAGE_ID=$(start-cli s9pk inspect *.s9pk manifest | jq -r '.id') - echo "package_id=${PACKAGE_ID}" >> $GITHUB_ENV - printf "\n SHA256SUM: $(sha256sum ${PACKAGE_ID}.s9pk) \n" - shell: bash - - - name: Upload .s9pk - uses: actions/upload-artifact@v5 - with: - name: ${{ env.package_id }}.s9pk - path: ./${{ env.package_id }}.s9pk \ No newline at end of file + build: + uses: start9labs/shared-workflows/.github/workflows/buildService.yml@master + secrets: + DEV_KEY: ${{ secrets.DEV_KEY }} diff --git a/.github/workflows/releaseService.yml b/.github/workflows/releaseService.yml index f55f1b3..4764532 100644 --- a/.github/workflows/releaseService.yml +++ b/.github/workflows/releaseService.yml @@ -6,78 +6,11 @@ on: - 'v*.*' jobs: - ReleasePackage: - runs-on: ubuntu-latest + release: + uses: start9labs/shared-workflows/.github/workflows/releaseService.yml@master + with: + REGISTRY: ${{ vars.REGISTRY }} # Optional. Defaults to https://alpha-registry-x.start9.com + secrets: + DEV_KEY: ${{ secrets.DEV_KEY }} # Required permissions: contents: write - steps: - - name: Prepare StartOS SDK - uses: start9labs/sdk@v2 - - - name: Checkout services repository - uses: actions/checkout@v5 - with: - submodules: recursive - - - name: Build the service package - id: build - env: - S9DEVKEY: ${{ secrets.S9DEVKEY }} - run: | - start-cli init-key - if [[ -n "$S9DEVKEY" ]]; then - echo "Using developer key from secrets to sign the package." - printf '%s' "$S9DEVKEY" > ~/.startos/developer.key.pem - else - echo "Using newly generated developer key to sign the package." - fi - RUST_LOG=debug RUST_BACKTRACE=1 make - sleep 2 - MANIFEST_JSON=$(start-cli s9pk inspect *.s9pk manifest) - PACKAGE_ID=$(echo "$MANIFEST_JSON" | jq -r '.id') - PACKAGE_TITLE=$(echo "$MANIFEST_JSON" | jq -r '.title') - echo "package_id=${PACKAGE_ID}" >> $GITHUB_ENV - echo "package_title=${PACKAGE_TITLE}" >> $GITHUB_ENV - printf "\n SHA256SUM: $(sha256sum ${PACKAGE_ID}.s9pk) \n" - shell: bash - - - name: Generate sha256 checksum - run: | - sha256sum ${{ env.package_id }}.s9pk > ${{ env.package_id }}.s9pk.sha256 - shell: bash - - - name: Generate changelog - run: | - echo "## What's Changed" > change-log.txt - echo "" >> change-log.txt - RELEASE_NOTES=$(start-cli s9pk inspect *.s9pk manifest | jq -r '.releaseNotes') - echo "${RELEASE_NOTES}" >> change-log.txt - echo "## SHA256 Hash" >> change-log.txt - echo '```' >> change-log.txt - sha256sum ${{ env.package_id }}.s9pk >> change-log.txt - echo '```' >> change-log.txt - shell: bash - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ github.ref_name }} - name: ${{ env.package_title }} ${{ github.ref_name }} - prerelease: true - body_path: change-log.txt - files: | - ./${{ env.package_id }}.s9pk - ./${{ env.package_id }}.s9pk.sha256 - - - name: Publish to Registry - env: - S9DEVKEY: ${{ secrets.S9DEVKEY }} - S9REGISTRY: ${{ secrets.S9REGISTRY }} - run: | - if [[ -z "$S9DEVKEY" || -z "$S9REGISTRY" ]]; then - echo "Publish skipped: One or both of S9DEVKEY and S9REGISTRY secrets are not set." - else - echo "Publishing package to registry..." - start-cli --registry https://$S9REGISTRY registry package add ${{ env.package_id }}.s9pk ${{ github.server_url }}/${{ github.repository }}/releases/download/${{ github.ref_name }}/${{ env.package_id }}.s9pk - fi - shell: bash diff --git a/startos/install/versions/v1.3.1_1-beta.3.ts b/startos/install/versions/v1.3.1_1-beta.3.ts index 98744f4..81b34a2 100644 --- a/startos/install/versions/v1.3.1_1-beta.3.ts +++ b/startos/install/versions/v1.3.1_1-beta.3.ts @@ -9,33 +9,31 @@ export const v1_3_1_1 = VersionInfo.of({ releaseNotes: 'Revamped for StartOS 0.4.0', migrations: { up: async ({ effects }) => { - await envFile.write(effects, envDefaults) - try { - const configYaml = load( - await readFile( - '/media/startos/volumes/main/start9/config.yaml', - 'utf8', - ), - ) as { implementation: 'LndRestWallet' | 'CLightningWallet' } + const raw = await readFile( + '/media/startos/volumes/main/start9/config.yaml', + 'utf8', + ).catch(console.log) + + if (raw) { + const configYaml = load(raw) as { + implementation: 'LndRestWallet' | 'CLightningWallet' + } const configuredImplementation = configYaml.implementation === 'CLightningWallet' ? 'CoreLightningWallet' : 'LndRestWallet' - console.log('configuredImplementation', configuredImplementation) - - await envFile.merge(effects, { + await envFile.write(effects, { + ...envDefaults, LNBITS_BACKEND_WALLET_CLASS: configuredImplementation, LNBITS_ALLOWED_FUNDING_SOURCES: configuredImplementation, }) - - rm('/media/startos/volumes/main/start9', { - recursive: true, - }).catch(console.error) - } catch { - console.log('config.yaml not found') } + + rm('/media/startos/volumes/main/start9', { + recursive: true, + }).catch(console.error) }, down: IMPOSSIBLE, }, diff --git a/startos/manifest.ts b/startos/manifest.ts index 59ca5da..2af084b 100644 --- a/startos/manifest.ts +++ b/startos/manifest.ts @@ -1,10 +1,4 @@ import { setupManifest } from '@start9labs/start-sdk' -import { SDKImageInputSpec } from '@start9labs/start-sdk/base/lib/types/ManifestTypes' - -const BUILD = process.env.BUILD || '' - -const architectures = - BUILD === 'x86_64' || BUILD === 'aarch64' ? [BUILD] : ['x86_64', 'aarch64'] export const manifest = setupManifest({ id: 'lnbits', @@ -25,22 +19,9 @@ export const manifest = setupManifest({ images: { lnbits: { source: { - dockerBuild: { - dockerfile: 'Dockerfile', - workdir: '.', - }, + dockerBuild: {}, }, - arch: architectures, - } as SDKImageInputSpec, - }, - hardwareRequirements: {}, - alerts: { - install: null, - update: null, - uninstall: null, - restore: null, - start: null, - stop: null, + }, }, dependencies: { 'c-lightning': { From c11eecaf231ef19b2ae85ff9a61d4fef5566a446 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Sun, 21 Dec 2025 18:33:35 -0700 Subject: [PATCH 39/49] better migratio --- package-lock.json | 29 +-------------------- package.json | 4 +-- startos/install/versions/v1.3.1_1-beta.3.ts | 28 ++++++++++---------- 3 files changed, 16 insertions(+), 45 deletions(-) diff --git a/package-lock.json b/package-lock.json index 775cdc6..b5f425d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,11 +6,9 @@ "": { "name": "lnbits-startos", "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.45", - "js-yaml": "^4.1.0" + "@start9labs/start-sdk": "^0.4.0-beta.45" }, "devDependencies": { - "@types/js-yaml": "^4.0.9", "@types/node": "^22.16.2", "@vercel/ncc": "^0.38.3", "prettier": "^3.6.2", @@ -74,13 +72,6 @@ "integrity": "sha512-MIyNUZipBTbyUNnhvuXJTY7B6qNI78meck9Jbv3wk0OgNwRyOOVEKDutAkOs1snB/tx0FafyR6/SN4Ps0hZPeg==", "license": "MIT" }, - "node_modules/@types/js-yaml": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", - "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/node": { "version": "22.19.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", @@ -101,12 +92,6 @@ "ncc": "dist/ncc/cli.js" } }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, "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", @@ -135,18 +120,6 @@ "whatwg-fetch": "^3.4.1" } }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/mime": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz", diff --git a/package.json b/package.json index 70109cc..5c6bbc5 100644 --- a/package.json +++ b/package.json @@ -6,11 +6,9 @@ "check": "tsc --noEmit" }, "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.45", - "js-yaml": "^4.1.0" + "@start9labs/start-sdk": "^0.4.0-beta.45" }, "devDependencies": { - "@types/js-yaml": "^4.0.9", "@types/node": "^22.16.2", "@vercel/ncc": "^0.38.3", "prettier": "^3.6.2", diff --git a/startos/install/versions/v1.3.1_1-beta.3.ts b/startos/install/versions/v1.3.1_1-beta.3.ts index 81b34a2..3382471 100644 --- a/startos/install/versions/v1.3.1_1-beta.3.ts +++ b/startos/install/versions/v1.3.1_1-beta.3.ts @@ -1,7 +1,6 @@ -import { VersionInfo, IMPOSSIBLE } from '@start9labs/start-sdk' +import { VersionInfo, IMPOSSIBLE, YAML } from '@start9labs/start-sdk' import { envFile } from '../../fileModels/env' import { envDefaults } from '../../utils' -import { load } from 'js-yaml' import { readFile, rm } from 'fs/promises' export const v1_3_1_1 = VersionInfo.of({ @@ -9,16 +8,17 @@ export const v1_3_1_1 = VersionInfo.of({ releaseNotes: 'Revamped for StartOS 0.4.0', migrations: { up: async ({ effects }) => { - const raw = await readFile( + // get old config.yaml + const configYaml: + | { + implementation: 'LndRestWallet' | 'CLightningWallet' + } + | undefined = await readFile( '/media/startos/volumes/main/start9/config.yaml', - 'utf8', - ).catch(console.log) - - if (raw) { - const configYaml = load(raw) as { - implementation: 'LndRestWallet' | 'CLightningWallet' - } + 'utf-8', + ).then(YAML.parse, () => undefined) + if (configYaml) { const configuredImplementation = configYaml.implementation === 'CLightningWallet' ? 'CoreLightningWallet' @@ -29,11 +29,11 @@ export const v1_3_1_1 = VersionInfo.of({ LNBITS_BACKEND_WALLET_CLASS: configuredImplementation, LNBITS_ALLOWED_FUNDING_SOURCES: configuredImplementation, }) - } - rm('/media/startos/volumes/main/start9', { - recursive: true, - }).catch(console.error) + rm('/media/startos/volumes/main/start9', { + recursive: true, + }).catch(console.error) + } }, down: IMPOSSIBLE, }, From dfa9e86f5a0add63d66d64ae1bede4970e35b7cd Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Fri, 26 Dec 2025 10:27:18 -0700 Subject: [PATCH 40/49] Update upstream to 1.4.0 --- Dockerfile | 4 ++-- startos/install/versions/index.ts | 2 +- .../versions/{v1.3.1_1-beta.3.ts => v1.4.0_1-beta.0.ts} | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename startos/install/versions/{v1.3.1_1-beta.3.ts => v1.4.0_1-beta.0.ts} (94%) diff --git a/Dockerfile b/Dockerfile index 6f06bb2..ab5d01a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM lnbits/lnbits:v1.3.1 AS builder +FROM lnbits/lnbits:v1.4.0 AS builder # arm64 or amd64 ARG PLATFORM @@ -6,7 +6,7 @@ ARG PLATFORM RUN apt-get update && apt-get install -y bash curl sqlite3 tini --no-install-recommends RUN curl -sS https://webi.sh/yq | sh -FROM lnbits/lnbits:v1.3.1 AS final +FROM lnbits/lnbits:v1.4.0 AS final COPY --from=builder /usr/bin/tini /usr/bin/tini COPY --from=builder /usr/bin/sqlite3 /usr/bin/sqlite3 diff --git a/startos/install/versions/index.ts b/startos/install/versions/index.ts index 6ed7145..2aba7a7 100644 --- a/startos/install/versions/index.ts +++ b/startos/install/versions/index.ts @@ -1,3 +1,3 @@ -export { v1_3_1_1 as current } from './v1.3.1_1-beta.3' +export { v1_4_0_1 as current } from './v1.4.0_1-beta.0' export const other = [] diff --git a/startos/install/versions/v1.3.1_1-beta.3.ts b/startos/install/versions/v1.4.0_1-beta.0.ts similarity index 94% rename from startos/install/versions/v1.3.1_1-beta.3.ts rename to startos/install/versions/v1.4.0_1-beta.0.ts index 3382471..5cbe665 100644 --- a/startos/install/versions/v1.3.1_1-beta.3.ts +++ b/startos/install/versions/v1.4.0_1-beta.0.ts @@ -3,8 +3,8 @@ import { envFile } from '../../fileModels/env' import { envDefaults } from '../../utils' import { readFile, rm } from 'fs/promises' -export const v1_3_1_1 = VersionInfo.of({ - version: '1.3.1:1-beta.3', +export const v1_4_0_1 = VersionInfo.of({ + version: '1.4.0:1-beta.0', releaseNotes: 'Revamped for StartOS 0.4.0', migrations: { up: async ({ effects }) => { From 4eafa5b2c8fc6728317f8b2a0681fb5ceef22a1a Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Fri, 26 Dec 2025 10:27:31 -0700 Subject: [PATCH 41/49] bump deps --- startos/dependencies.ts | 4 ++-- startos/manifest.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/startos/dependencies.ts b/startos/dependencies.ts index ac92460..5ace8cf 100644 --- a/startos/dependencies.ts +++ b/startos/dependencies.ts @@ -11,7 +11,7 @@ export const setDependencies = sdk.setupDependencies(async ({ effects }) => { lnd: { healthChecks: ['primary'], kind: 'running', - versionRange: '>=0.19.3-beta:1-beta.0', + versionRange: '>=0.20.0-beta:1-beta.2', }, } } else if (configuredLnImplementation === 'CoreLightningWallet') { @@ -19,7 +19,7 @@ export const setDependencies = sdk.setupDependencies(async ({ effects }) => { 'c-lightning': { healthChecks: ['lightningd'], kind: 'running', - versionRange: '>=25.9.3:1-beta.1', + versionRange: '>=25.12:1-beta.1', }, } } diff --git a/startos/manifest.ts b/startos/manifest.ts index 2af084b..51b667b 100644 --- a/startos/manifest.ts +++ b/startos/manifest.ts @@ -27,12 +27,12 @@ export const manifest = setupManifest({ 'c-lightning': { description: 'Optionally connect RTL to your CLN node.', optional: true, - s9pk: 'https://github.com/Start9Labs/cln-startos/releases/download/v25.12.0.1-beta.0/c-lightning.s9pk', + s9pk: 'https://github.com/Start9Labs/cln-startos/releases/download/v25.12.0_1-beta.1/c-lightning.s9pk', }, lnd: { description: 'Optionally connect RTL to your LND node.', optional: true, - s9pk: 'https://github.com/Start9Labs/lnd-startos/releases/download/v0.20.0-beta.1-beta.1/lnd.s9pk', + s9pk: 'https://github.com/Start9Labs/lnd-startos/releases/download/v0.20.0-beta.1-beta.2/lnd.s9pk', }, }, }) From af857e92c1cd3efc97b0c3cfb2dd1f3790e00681 Mon Sep 17 00:00:00 2001 From: Dominion5254 Date: Thu, 5 Feb 2026 10:30:03 -0700 Subject: [PATCH 42/49] Update/1.4.2 (#57) * beta.48 * latest CI * update upstream to v1.4.2 * use metadata for dependencies * arches and fix dep icons --- .github/workflows/buildService.yml | 5 + Dockerfile | 4 +- Makefile | 91 +--------------- package-lock.json | 20 ++-- package.json | 2 +- s9pk.mk | 103 ++++++++++++++++++ startos/actions/lightningImplementation.ts | 23 ++-- startos/actions/resetPassword.ts | 10 +- startos/i18n/dictionaries/default.ts | 39 +++++++ startos/i18n/dictionaries/translations.ts | 88 +++++++++++++++ startos/i18n/index.ts | 8 ++ startos/init/taskSetLnImplementation.ts | 3 +- startos/install/versions/index.ts | 2 +- ...{v1.4.0_1-beta.0.ts => v1.4.2_1-beta.0.ts} | 4 +- startos/interfaces.ts | 5 +- startos/main.ts | 9 +- startos/manifest/i18n.ts | 41 +++++++ startos/{manifest.ts => manifest/index.ts} | 18 ++- 18 files changed, 345 insertions(+), 130 deletions(-) create mode 100644 s9pk.mk create mode 100644 startos/i18n/dictionaries/default.ts create mode 100644 startos/i18n/dictionaries/translations.ts create mode 100644 startos/i18n/index.ts rename startos/install/versions/{v1.4.0_1-beta.0.ts => v1.4.2_1-beta.0.ts} (94%) create mode 100644 startos/manifest/i18n.ts rename startos/{manifest.ts => manifest/index.ts} (62%) diff --git a/.github/workflows/buildService.yml b/.github/workflows/buildService.yml index 5d19b77..1b75af4 100644 --- a/.github/workflows/buildService.yml +++ b/.github/workflows/buildService.yml @@ -9,8 +9,13 @@ on: paths-ignore: ['*.md'] branches: ['main', 'master', 'update/040'] +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + jobs: build: + if: github.event.pull_request.draft == false uses: start9labs/shared-workflows/.github/workflows/buildService.yml@master secrets: DEV_KEY: ${{ secrets.DEV_KEY }} diff --git a/Dockerfile b/Dockerfile index ab5d01a..83eda4c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM lnbits/lnbits:v1.4.0 AS builder +FROM lnbits/lnbits:v1.4.2 AS builder # arm64 or amd64 ARG PLATFORM @@ -6,7 +6,7 @@ ARG PLATFORM RUN apt-get update && apt-get install -y bash curl sqlite3 tini --no-install-recommends RUN curl -sS https://webi.sh/yq | sh -FROM lnbits/lnbits:v1.4.0 AS final +FROM lnbits/lnbits:v1.4.2 AS final COPY --from=builder /usr/bin/tini /usr/bin/tini COPY --from=builder /usr/bin/sqlite3 /usr/bin/sqlite3 diff --git a/Makefile b/Makefile index fce5dc8..d471333 100644 --- a/Makefile +++ b/Makefile @@ -1,88 +1,3 @@ -PACKAGE_ID := $(shell awk -F"'" '/id:/ {print $$2}' startos/manifest.ts) -INGREDIENTS := $(shell start-cli s9pk list-ingredients 2>/dev/null) - -.PHONY: all 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 '.hardwareRequirements.arch | 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: $(PACKAGE_ID).s9pk - $(call SUMMARY,$<) - -arch/%: $(PACKAGE_ID)_%.s9pk - $(call SUMMARY,$<) - -x86 x86_64: arch/x86_64 -arm arm64 aarch64: arch/aarch64 -riscv riscv64: arch/riscv64 - -$(PACKAGE_ID).s9pk: $(INGREDIENTS) .git/HEAD .git/index - @$(MAKE) --no-print-directory ingredients - @echo " Packing '$@'..." - start-cli s9pk pack -o $@ - -$(PACKAGE_ID)_%.s9pk: $(INGREDIENTS) .git/HEAD .git/index - @$(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" - -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 build - -node_modules: package-lock.json - npm ci - -package-lock.json: package.json - npm i - -clean: - @echo "Cleaning up build artifacts..." \ No newline at end of file +ARCHES ?= x86 arm +# overrides to s9pk.mk must precede the include statement +include s9pk.mk diff --git a/package-lock.json b/package-lock.json index b5f425d..88e55e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "lnbits-startos", "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.45" + "@start9labs/start-sdk": "0.4.0-beta.48" }, "devDependencies": { "@types/node": "^22.16.2", @@ -49,9 +49,9 @@ } }, "node_modules/@start9labs/start-sdk": { - "version": "0.4.0-beta.45", - "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.45.tgz", - "integrity": "sha512-N4iIifr5f1ElB4zcZnW8hCkPOAsfCxf+1UGqp249Tzm+gIRj45aq1wiE+vGCeSyGIuHsfMp3h5Pouyvfn33FOQ==", + "version": "0.4.0-beta.48", + "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.48.tgz", + "integrity": "sha512-beMdwhUffhnbSm3FgkWPJjAWazMhNMzqbHtS6yK2hX6VpP39JxzDVo8vEbJAUfot6LPw+OhOUtPEu6KDaGre6A==", "license": "MIT", "dependencies": { "@iarna/toml": "^3.0.0", @@ -73,9 +73,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.19.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", - "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", + "version": "22.19.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.8.tgz", + "integrity": "sha512-ebO/Yl+EAvVe8DnMfi+iaAyIqYdK0q/q0y0rw82INWEKJOBe6b/P3YWE8NW7oOlF/nXFNrHwhARrN/hdgDkraA==", "dev": true, "license": "MIT", "dependencies": { @@ -165,9 +165,9 @@ } }, "node_modules/prettier": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", - "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", + "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": { diff --git a/package.json b/package.json index 5c6bbc5..7b583a2 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "check": "tsc --noEmit" }, "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.45" + "@start9labs/start-sdk": "0.4.0-beta.48" }, "devDependencies": { "@types/node": "^22.16.2", diff --git a/s9pk.mk b/s9pk.mk new file mode 100644 index 0000000..9d887af --- /dev/null +++ b/s9pk.mk @@ -0,0 +1,103 @@ +# ** 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) +ARCHES ?= x86 arm riscv +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) + +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/HEAD .git/index + @$(MAKE) --no-print-directory ingredients + @echo " Packing '$@'..." + start-cli s9pk pack -o $@ + +$(BASE_NAME)_%.s9pk: $(INGREDIENTS) .git/HEAD .git/index + @$(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" + +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 build + +node_modules: package-lock.json + npm ci + +package-lock.json: package.json + npm i + +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 diff --git a/startos/actions/lightningImplementation.ts b/startos/actions/lightningImplementation.ts index 79edd74..e73d7f9 100644 --- a/startos/actions/lightningImplementation.ts +++ b/startos/actions/lightningImplementation.ts @@ -1,17 +1,19 @@ import { access, rm } from 'fs/promises' import { envFile } from '../fileModels/env' +import { i18n } from '../i18n' import { sdk } from '../sdk' const { InputSpec, Value } = sdk export const inputSpec = InputSpec.of({ implementation: Value.select({ - name: 'Lightning Implementation', - description: + name: i18n('Lightning Implementation'), + description: i18n( 'The underlying Lightning implementation, currently LND or Core Lightning (CLN)', + ), values: { - LndRestWallet: 'LND', - CoreLightningWallet: 'Core Lightning', + LndRestWallet: i18n('LND'), + CoreLightningWallet: i18n('Core Lightning'), }, default: undefined as any, }), @@ -23,10 +25,13 @@ export const setLnImplementation = sdk.Action.withInput( // metadata async ({ effects }) => ({ - name: 'Lightning Implementation', - description: 'Select the Lightning Implementation for LNbits to utilize', - warning: + name: i18n('Lightning Implementation'), + description: i18n( + 'Select the Lightning Implementation for LNbits to utilize', + ), + warning: i18n( 'If the LN implementation is changed after using LNBits this will delete all LNBits accounts and wallets related to the previously configured LN implementation! All LN funds will still be available on the underlying LN implementation.', + ), allowedStatuses: 'any', group: null, visibility: 'enabled', @@ -58,12 +63,12 @@ export const setLnImplementation = sdk.Action.withInput( await access('/media/startos/volumes/main/database.sqlite3') if (configuredLnImplementation !== input.implementation) { console.log( - 'existing LN implementation does not match input. Resetting DB...', + i18n('Existing LN implementation does not match input. Resetting DB...'), ) await rm('/media/startos/volumes/main/database.sqlite3') } } catch (error) { - console.log('DB has not been initialized') + console.log(i18n('DB has not been initialized')) } await envFile.merge(effects, { diff --git a/startos/actions/resetPassword.ts b/startos/actions/resetPassword.ts index d0d10bf..ed2ca18 100644 --- a/startos/actions/resetPassword.ts +++ b/startos/actions/resetPassword.ts @@ -1,3 +1,4 @@ +import { i18n } from '../i18n' import { sdk } from '../sdk' import { db, mainMounts, randomPassword } from '../utils' import { utils } from '@start9labs/start-sdk' @@ -8,9 +9,10 @@ export const resetPassword = sdk.Action.withoutInput( // metadata async ({ effects }) => ({ - name: 'Reset Password', - description: + name: i18n('Reset Password'), + description: i18n( 'Reset Password for the super_user in the event of a lost or forgotten password', + ), warning: null, allowedStatuses: 'only-running', group: null, @@ -55,8 +57,8 @@ export const resetPassword = sdk.Action.withoutInput( ) return { version: '1', - title: 'Success', - message: `The new Super User password for '${res.stdout}' is below`, + title: i18n('Success'), + message: i18n('The new Super User password is below'), result: { type: 'single', value: newPassword, diff --git a/startos/i18n/dictionaries/default.ts b/startos/i18n/dictionaries/default.ts new file mode 100644 index 0000000..af75661 --- /dev/null +++ b/startos/i18n/dictionaries/default.ts @@ -0,0 +1,39 @@ +export const DEFAULT_LANG = 'en_US' + +const dict = { + // main.ts + 'Starting LNbits!': 0, + 'Web Interface': 1, + 'The web interface is ready': 2, + 'The web interface is not ready': 3, + + // interfaces.ts + 'Web UI': 4, + 'The web interface of LNbits': 5, + + // actions/lightningImplementation.ts + 'Lightning Implementation': 6, + 'The underlying Lightning implementation, currently LND or Core Lightning (CLN)': 7, + 'LND': 8, + 'Core Lightning': 9, + 'Select the Lightning Implementation for LNbits to utilize': 10, + 'If the LN implementation is changed after using LNBits this will delete all LNBits accounts and wallets related to the previously configured LN implementation! All LN funds will still be available on the underlying LN implementation.': 11, + 'Existing LN implementation does not match input. Resetting DB...': 17, + 'DB has not been initialized': 18, + + // actions/resetPassword.ts + 'Reset Password': 12, + 'Reset Password for the super_user in the event of a lost or forgotten password': 13, + 'Success': 14, + 'The new Super User password is below': 15, + + // init/taskSetLnImplementation.ts + 'LNbits requires an underlying Lightning node!': 16, +} as const + +/** + * Plumbing. DO NOT EDIT. + */ +export type I18nKey = keyof typeof dict +export type LangDict = Record<(typeof dict)[I18nKey], string> +export default dict diff --git a/startos/i18n/dictionaries/translations.ts b/startos/i18n/dictionaries/translations.ts new file mode 100644 index 0000000..8fe99ce --- /dev/null +++ b/startos/i18n/dictionaries/translations.ts @@ -0,0 +1,88 @@ +import { LangDict } from './default' + +export default { + es_ES: { + 0: '¡Iniciando LNbits!', + 1: 'Interfaz web', + 2: 'La interfaz web está lista', + 3: 'La interfaz web no está lista', + 4: 'Interfaz web', + 5: 'La interfaz web de LNbits', + 6: 'Implementación Lightning', + 7: 'La implementación Lightning subyacente, actualmente LND o Core Lightning (CLN)', + 8: 'LND', + 9: 'Core Lightning', + 10: 'Seleccione la implementación Lightning que LNbits utilizará', + 11: '¡Si se cambia la implementación LN después de usar LNBits, se eliminarán todas las cuentas y billeteras de LNBits relacionadas con la implementación LN configurada anteriormente! Todos los fondos LN seguirán disponibles en la implementación LN subyacente.', + 12: 'Restablecer contraseña', + 13: 'Restablecer la contraseña del super_usuario en caso de contraseña perdida u olvidada', + 14: 'Éxito', + 15: 'La nueva contraseña del Super Usuario se muestra a continuación', + 16: '¡LNbits requiere un nodo Lightning subyacente!', + 17: 'La implementación LN existente no coincide con la entrada. Restableciendo BD...', + 18: 'La BD no ha sido inicializada', + } satisfies LangDict, + de_DE: { + 0: 'LNbits wird gestartet!', + 1: 'Web-Oberfläche', + 2: 'Die Web-Oberfläche ist bereit', + 3: 'Die Web-Oberfläche ist nicht bereit', + 4: 'Web-Oberfläche', + 5: 'Die Web-Oberfläche von LNbits', + 6: 'Lightning-Implementierung', + 7: 'Die zugrunde liegende Lightning-Implementierung, derzeit LND oder Core Lightning (CLN)', + 8: 'LND', + 9: 'Core Lightning', + 10: 'Wählen Sie die Lightning-Implementierung, die LNbits verwenden soll', + 11: 'Wenn die LN-Implementierung nach der Nutzung von LNBits geändert wird, werden alle LNBits-Konten und Wallets der zuvor konfigurierten LN-Implementierung gelöscht! Alle LN-Guthaben bleiben auf der zugrunde liegenden LN-Implementierung verfügbar.', + 12: 'Passwort zurücksetzen', + 13: 'Passwort für den Super_User zurücksetzen, falls das Passwort verloren gegangen oder vergessen wurde', + 14: 'Erfolg', + 15: 'Das neue Super-User-Passwort wird unten angezeigt', + 16: 'LNbits benötigt einen zugrunde liegenden Lightning-Knoten!', + 17: 'Bestehende LN-Implementierung stimmt nicht mit der Eingabe überein. Datenbank wird zurückgesetzt...', + 18: 'Datenbank wurde nicht initialisiert', + } satisfies LangDict, + pl_PL: { + 0: 'Uruchamianie LNbits!', + 1: 'Interfejs webowy', + 2: 'Interfejs webowy jest gotowy', + 3: 'Interfejs webowy nie jest gotowy', + 4: 'Interfejs webowy', + 5: 'Interfejs webowy LNbits', + 6: 'Implementacja Lightning', + 7: 'Podstawowa implementacja Lightning, obecnie LND lub Core Lightning (CLN)', + 8: 'LND', + 9: 'Core Lightning', + 10: 'Wybierz implementację Lightning, z której LNbits będzie korzystać', + 11: 'Jeśli implementacja LN zostanie zmieniona po użyciu LNBits, wszystkie konta i portfele LNBits powiązane z poprzednio skonfigurowaną implementacją LN zostaną usunięte! Wszystkie środki LN będą nadal dostępne na podstawowej implementacji LN.', + 12: 'Resetuj hasło', + 13: 'Resetuj hasło super_użytkownika w przypadku zgubienia lub zapomnienia hasła', + 14: 'Sukces', + 15: 'Nowe hasło Super Użytkownika jest wyświetlone poniżej', + 16: 'LNbits wymaga podstawowego węzła Lightning!', + 17: 'Istniejąca implementacja LN nie pasuje do danych wejściowych. Resetowanie bazy danych...', + 18: 'Baza danych nie została zainicjalizowana', + } satisfies LangDict, + fr_FR: { + 0: 'Démarrage de LNbits !', + 1: 'Interface web', + 2: "L'interface web est prête", + 3: "L'interface web n'est pas prête", + 4: 'Interface web', + 5: "L'interface web de LNbits", + 6: 'Implémentation Lightning', + 7: "L'implémentation Lightning sous-jacente, actuellement LND ou Core Lightning (CLN)", + 8: 'LND', + 9: 'Core Lightning', + 10: "Sélectionnez l'implémentation Lightning que LNbits utilisera", + 11: "Si l'implémentation LN est modifiée après l'utilisation de LNBits, tous les comptes et portefeuilles LNBits liés à l'implémentation LN précédemment configurée seront supprimés ! Tous les fonds LN resteront disponibles sur l'implémentation LN sous-jacente.", + 12: 'Réinitialiser le mot de passe', + 13: "Réinitialiser le mot de passe du super_utilisateur en cas de mot de passe perdu ou oublié", + 14: 'Succès', + 15: 'Le nouveau mot de passe du Super Utilisateur est affiché ci-dessous', + 16: 'LNbits nécessite un nœud Lightning sous-jacent !', + 17: "L'implémentation LN existante ne correspond pas à l'entrée. Réinitialisation de la BD...", + 18: "La BD n'a pas été initialisée", + } satisfies LangDict, +} as Record diff --git a/startos/i18n/index.ts b/startos/i18n/index.ts new file mode 100644 index 0000000..04cea20 --- /dev/null +++ b/startos/i18n/index.ts @@ -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) diff --git a/startos/init/taskSetLnImplementation.ts b/startos/init/taskSetLnImplementation.ts index f4827f0..45b1351 100644 --- a/startos/init/taskSetLnImplementation.ts +++ b/startos/init/taskSetLnImplementation.ts @@ -1,11 +1,12 @@ import { setLnImplementation } from '../actions/lightningImplementation' +import { i18n } from '../i18n' import { sdk } from '../sdk' export const taskSetLnImplementation = sdk.setupOnInit( async (effects, kind) => { if (kind === 'install') { await sdk.action.createOwnTask(effects, setLnImplementation, 'critical', { - reason: 'LNbits requires an underlying Lightning node!', + reason: i18n('LNbits requires an underlying Lightning node!'), }) } }, diff --git a/startos/install/versions/index.ts b/startos/install/versions/index.ts index 2aba7a7..72d9ef2 100644 --- a/startos/install/versions/index.ts +++ b/startos/install/versions/index.ts @@ -1,3 +1,3 @@ -export { v1_4_0_1 as current } from './v1.4.0_1-beta.0' +export { v1_4_2_1 as current } from './v1.4.2_1-beta.0' export const other = [] diff --git a/startos/install/versions/v1.4.0_1-beta.0.ts b/startos/install/versions/v1.4.2_1-beta.0.ts similarity index 94% rename from startos/install/versions/v1.4.0_1-beta.0.ts rename to startos/install/versions/v1.4.2_1-beta.0.ts index 5cbe665..f60bc0d 100644 --- a/startos/install/versions/v1.4.0_1-beta.0.ts +++ b/startos/install/versions/v1.4.2_1-beta.0.ts @@ -3,8 +3,8 @@ import { envFile } from '../../fileModels/env' import { envDefaults } from '../../utils' import { readFile, rm } from 'fs/promises' -export const v1_4_0_1 = VersionInfo.of({ - version: '1.4.0:1-beta.0', +export const v1_4_2_1 = VersionInfo.of({ + version: '1.4.2:1-beta.0', releaseNotes: 'Revamped for StartOS 0.4.0', migrations: { up: async ({ effects }) => { diff --git a/startos/interfaces.ts b/startos/interfaces.ts index 8b061a0..46e4579 100644 --- a/startos/interfaces.ts +++ b/startos/interfaces.ts @@ -1,3 +1,4 @@ +import { i18n } from './i18n' import { sdk } from './sdk' import { uiPort } from './utils' @@ -7,9 +8,9 @@ export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => { protocol: 'http', }) const ui = sdk.createInterface(effects, { - name: 'Web UI', + name: i18n('Web UI'), id: 'ui', - description: 'The web interface of LNbits', + description: i18n('The web interface of LNbits'), type: 'ui', masked: false, schemeOverride: null, diff --git a/startos/main.ts b/startos/main.ts index dd85453..368ea85 100644 --- a/startos/main.ts +++ b/startos/main.ts @@ -1,4 +1,5 @@ import { envFile } from './fileModels/env' +import { i18n } from './i18n' import { sdk } from './sdk' import { mainMounts, uiPort } from './utils' @@ -8,7 +9,7 @@ export const main = sdk.setupMain(async ({ effects }) => { * * In this section, we fetch any resources or run any desired preliminary commands. */ - console.info('Starting LNbits!') + console.info(i18n('Starting LNbits!')) const configuredLnImplementation = await envFile .read((e) => e.LNBITS_BACKEND_WALLET_CLASS) @@ -44,12 +45,12 @@ export const main = sdk.setupMain(async ({ effects }) => { subcontainer: lnbitsSub, exec: { command: ['uv', 'run', 'lnbits'], env: env || {} }, ready: { - display: 'Web Interface', + display: i18n('Web Interface'), gracePeriod: 75_000, fn: () => sdk.healthCheck.checkPortListening(effects, uiPort, { - successMessage: 'The web interface is ready', - errorMessage: 'The web interface is not ready', + successMessage: i18n('The web interface is ready'), + errorMessage: i18n('The web interface is not ready'), }), }, requires: [], diff --git a/startos/manifest/i18n.ts b/startos/manifest/i18n.ts new file mode 100644 index 0000000..d02253c --- /dev/null +++ b/startos/manifest/i18n.ts @@ -0,0 +1,41 @@ +export const short = { + en_US: + 'Free and open-source lightning-network wallet/accounts system.', + es_ES: + 'Sistema de billetera/cuentas de red Lightning gratuito y de código abierto.', + de_DE: + 'Kostenloses und quelloffenes Lightning-Netzwerk-Wallet-/Kontensystem.', + pl_PL: + 'Darmowy i otwartoźródłowy system portfela/kont sieci Lightning.', + fr_FR: + 'Système de portefeuille/comptes du réseau Lightning gratuit et open-source.', +} + +export const depClnTitle = { + en_US: 'Core Lightning', + es_ES: 'Core Lightning', + de_DE: 'Core Lightning', + pl_PL: 'Core Lightning', + fr_FR: 'Core Lightning', +} + +export const depLndTitle = { + en_US: 'LND', + es_ES: 'LND', + de_DE: 'LND', + pl_PL: 'LND', + fr_FR: 'LND', +} + +export const long = { + en_US: + 'A very simple Python server that sits on top of any funding source, and can be used as an accounts system, extendable platform, development stack, fallback wallet or even instant wallet for LN demonstrations', + es_ES: + 'Un servidor Python muy simple que se ejecuta sobre cualquier fuente de financiación, y puede usarse como sistema de cuentas, plataforma extensible, pila de desarrollo, billetera de respaldo o incluso billetera instantánea para demostraciones de LN', + de_DE: + 'Ein sehr einfacher Python-Server, der auf jeder Finanzierungsquelle aufbaut und als Kontensystem, erweiterbare Plattform, Entwicklungsstack, Ersatz-Wallet oder sogar als Sofort-Wallet für LN-Demonstrationen verwendet werden kann', + pl_PL: + 'Bardzo prosty serwer Python, który działa na dowolnym źródle finansowania i może być używany jako system kont, rozszerzalna platforma, stos deweloperski, portfel zapasowy lub nawet portfel natychmiastowy do demonstracji LN', + fr_FR: + "Un serveur Python très simple qui fonctionne au-dessus de n'importe quelle source de financement, et peut être utilisé comme système de comptes, plateforme extensible, pile de développement, portefeuille de secours ou même portefeuille instantané pour les démonstrations LN", +} \ No newline at end of file diff --git a/startos/manifest.ts b/startos/manifest/index.ts similarity index 62% rename from startos/manifest.ts rename to startos/manifest/index.ts index 51b667b..d2edc89 100644 --- a/startos/manifest.ts +++ b/startos/manifest/index.ts @@ -1,4 +1,5 @@ import { setupManifest } from '@start9labs/start-sdk' +import { short, long, depClnTitle, depLndTitle } from './i18n' export const manifest = setupManifest({ id: 'lnbits', @@ -11,28 +12,33 @@ export const manifest = setupManifest({ donationUrl: 'https://demo.lnbits.com/tipjar/DwaUiE4kBX6mUW6pj3X5Kg', docsUrl: 'https://github.com/Start9Labs/lnbits-startos/blob/master/instructions.md', - description: { - short: 'Free and open-source lightning-network wallet/accounts system.', - long: 'A very simple Python server that sits on top of any funding source, and can be used as an accounts system, extendable platform, development stack, fallback wallet or even instant wallet for LN demonstrations', - }, + description: { short, long }, volumes: ['main'], images: { lnbits: { source: { dockerBuild: {}, }, + arch: ['aarch64', 'x86_64'], + emulateMissingAs: 'aarch64' }, }, dependencies: { 'c-lightning': { description: 'Optionally connect RTL to your CLN node.', optional: true, - s9pk: 'https://github.com/Start9Labs/cln-startos/releases/download/v25.12.0_1-beta.1/c-lightning.s9pk', + metadata: { + title: depClnTitle, + icon: 'https://github.com/Start9Labs/cln-startos/blob/master/icon.png?raw=true', + }, }, lnd: { description: 'Optionally connect RTL to your LND node.', optional: true, - s9pk: 'https://github.com/Start9Labs/lnd-startos/releases/download/v0.20.0-beta.1-beta.2/lnd.s9pk', + metadata: { + title: depLndTitle, + icon: 'https://github.com/Start9Labs/lnd-startos/blob/master/icon.png?raw=true', + }, }, }, }) From a41b9311f61654b0d623dd082979bd15798ab4eb Mon Sep 17 00:00:00 2001 From: Shadowy Super Coder Date: Sat, 7 Feb 2026 19:21:55 -0700 Subject: [PATCH 43/49] README and more --- .github/workflows/releaseService.yml | 7 +- CONTRIBUTING.md | 15 ++ Makefile | 2 +- README.md | 179 ++++++++++++++++++++- docs/instructions.md | 44 ----- docs/wallet-integrations/alby-extension.md | 27 ---- docs/wallet-integrations/bluewallet.md | 25 --- docs/wallet-integrations/zeus.md | 27 ---- s9pk.mk | 24 +++ startos/manifest/index.ts | 2 +- 10 files changed, 222 insertions(+), 130 deletions(-) create mode 100644 CONTRIBUTING.md delete mode 100644 docs/instructions.md delete mode 100644 docs/wallet-integrations/alby-extension.md delete mode 100644 docs/wallet-integrations/bluewallet.md delete mode 100644 docs/wallet-integrations/zeus.md diff --git a/.github/workflows/releaseService.yml b/.github/workflows/releaseService.yml index 4764532..d3bbee7 100644 --- a/.github/workflows/releaseService.yml +++ b/.github/workflows/releaseService.yml @@ -9,8 +9,11 @@ jobs: release: uses: start9labs/shared-workflows/.github/workflows/releaseService.yml@master with: - REGISTRY: ${{ vars.REGISTRY }} # Optional. Defaults to https://alpha-registry-x.start9.com + REGISTRY: ${{ vars.REGISTRY }} + S3_S9PKS_BASE_URL: ${{ vars.S3_S9PKS_BASE_URL }} secrets: - DEV_KEY: ${{ secrets.DEV_KEY }} # Required + DEV_KEY: ${{ secrets.DEV_KEY }} + S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }} + S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }} permissions: contents: write diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..663911b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,15 @@ +# Contributing + +## Building and Development + +See the [StartOS Packaging Guide](https://docs.start9.com/packaging-guide/) for complete environment setup and build instructions. + +### Quick Start + +```bash +# Install dependencies +npm ci + +# Build universal package +make +``` diff --git a/Makefile b/Makefile index d471333..10d4360 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,3 @@ -ARCHES ?= x86 arm +ARCHES := x86 arm # overrides to s9pk.mk must precede the include statement include s9pk.mk diff --git a/README.md b/README.md index 23ad797..8691b2c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,180 @@

- Project Logo + LNbits Logo

-# LNbits for StartOS +# LNbits on StartOS -This repo packages [LNbits](https://github.com/lnbits/lnbits) for StartOS. \ No newline at end of file +> **Upstream docs:** +> +> Everything not listed in this document should behave the same as upstream +> LNbits v1.4.2. If a feature, setting, or behavior is not mentioned +> here, the upstream documentation is accurate and fully applicable. + +A free and open-source lightning-network wallet/accounts system. See the [upstream repo](https://github.com/lnbits/lnbits) for general LNbits documentation. + +--- + +## 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](#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 | Custom Dockerfile based on `lnbits/lnbits:v1.4.2` | +| Architectures | x86_64, aarch64 | +| Entrypoint | `uv run lnbits` | + +The custom Dockerfile adds `sqlite3`, `tini`, `yq`, `xxd`, `curl`, `jq`, and `bcrypt` (Python) on top of the upstream image. These are used for the reset password action and other utilities. + +## Volume and Data Layout + +| Volume | Mount Point | Purpose | +|--------|-------------|---------| +| `main` | `/app/data` | All LNbits data (database, configuration) | + +StartOS-specific files on the `main` volume: + +| File | Purpose | +|------|---------| +| `.env` | LNbits environment configuration (managed by StartOS) | +| `store.json` | Not used (no store.json in this package) | + +The Lightning node volume is mounted read-only depending on the configured backend: + +| Backend | Mount Point | Files Used | +|---------|-------------|------------| +| LND | `/mnt/lnd` | `tls.cert`, `data/chain/bitcoin/mainnet/admin.macaroon` | +| CLN | `/mnt/cln` | `bitcoin/lightning-rpc` (Unix socket) | + +## Installation and First-Run Flow + +1. On install, StartOS creates a **critical task** prompting the user to select a Lightning implementation (LND or Core Lightning) +2. The `.env` file is written with the selected backend configuration +3. On first start, LNbits creates its SQLite database and presents a web UI for account creation +4. The first user to register becomes the **super user** (admin) + +## Configuration Management + +LNbits is configured via environment variables in the `.env` file, managed by StartOS. + +| StartOS-Managed | Details | +|-----------------|---------| +| Lightning backend | LND (REST) or Core Lightning (Unix socket) | + +Most LNbits settings are configurable through the **Admin UI** within LNbits itself (`LNBITS_ADMIN_UI=true`). StartOS manages the backend connection; everything else is configured through the LNbits web interface. + +Settings **not** managed by StartOS (hardcoded): + +| Setting | Value | Reason | +|---------|-------|--------| +| `HOST` | `lnbits.startos` | StartOS service networking | +| `PORT` | `5000` | Fixed internal port | +| `FORWARDED_ALLOW_IPS` | `*` | Required for HTTPS behind StartOS proxy | +| `LNBITS_DATA_FOLDER` | `./data` | Maps to the mounted volume | +| `LND_REST_ENDPOINT` | `https://lnd.startos:8080/` | StartOS service networking | +| `LND_REST_CERT` / `LND_REST_MACAROON` | Paths in `/mnt/lnd` | Mounted dependency volume | +| `CLIGHTNING_RPC` | `/mnt/cln/bitcoin/lightning-rpc` | Mounted dependency volume | +| `AUTH_ALLOWED_METHODS` | `username-password` | Only username/password auth | +| `LNBITS_ALLOWED_FUNDING_SOURCES` | Matches selected backend | Restricted to configured implementation | + +## Network Access and Interfaces + +| Interface | Port | Protocol | Purpose | +|-----------|------|----------|---------| +| Web UI | 5000 | HTTP | LNbits web interface and API | + +## Actions (StartOS UI) + +| Action | Purpose | Availability | Inputs | +|--------|---------|-------------|--------| +| **Lightning Implementation** | Select LND or Core Lightning as the backend | Any | Select: LND or CLN | +| **Reset Password** | Reset the super user password to a random value | Running only | None | + +**Warning:** Changing the Lightning implementation after initial use **deletes the LNbits database** (all accounts and wallets). Funds remain on the underlying Lightning node. + +## Backups and Restore + +**Backed up:** The entire `main` volume (database, `.env` configuration). + +**Restore behavior:** Standard restore — the database and configuration are restored as-is. The configured Lightning backend must be available. + +## Health Checks + +| Check | Method | Messages | +|-------|--------|----------| +| **Web Interface** | Port listening (5000), 75s grace period | Ready: "The web interface is ready" | + +## Dependencies + +| Dependency | Required | Version | Purpose | +|------------|----------|---------|---------| +| Core Lightning | Optional | `>=25.12:1-beta.1` | Lightning backend (if selected) | +| LND | Optional | `>=0.20.0-beta:1-beta.2` | Lightning backend (if selected) | + +One of the two Lightning implementations must be selected and running. The dependency is determined at runtime based on the configured `LNBITS_BACKEND_WALLET_CLASS`. + +## Limitations and Differences + +1. **Only LND and Core Lightning backends supported** — upstream supports many funding sources (LNPayWallet, OpenNodeWallet, EclairWallet, etc.); StartOS restricts to `LndRestWallet` and `CoreLightningWallet` +2. **SQLite only** — upstream supports PostgreSQL and CockroachDB; StartOS uses the embedded SQLite database +3. **Username/password auth only** — `AUTH_ALLOWED_METHODS` is set to `username-password`; other methods (e.g., Google OAuth) are not available +4. **Switching backends deletes the database** — changing from LND to CLN (or vice versa) removes the existing LNbits database +5. **Custom Docker image** — adds sqlite3 CLI, bcrypt, and other tools not in the upstream image + +## What Is Unchanged from Upstream + +- All LNbits extensions and the extension marketplace +- Wallet and account management +- Admin UI functionality +- API endpoints +- Invoice and payment handling +- LNURL support +- Theme customization (via Admin UI) + +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) for build instructions and development workflow. + +--- + +## Quick Reference for AI Consumers + +```yaml +package_id: lnbits +upstream_version: 1.4.2 +image: custom Dockerfile (based on lnbits/lnbits:v1.4.2) +architectures: [x86_64, aarch64] +volumes: + main: /app/data +ports: + ui: 5000 +dependencies: + - c-lightning (optional, >=25.12:1-beta.1) + - lnd (optional, >=0.20.0-beta:1-beta.2) +startos_managed_env_vars: + - LNBITS_BACKEND_WALLET_CLASS + - LNBITS_ALLOWED_FUNDING_SOURCES +actions: + - set-lightning-implementation + - reset-password +health_checks: + - port_listening: 5000 +backup_volumes: + - main +``` diff --git a/docs/instructions.md b/docs/instructions.md deleted file mode 100644 index 3e4a839..0000000 --- a/docs/instructions.md +++ /dev/null @@ -1,44 +0,0 @@ -# LNbits - -LNbits core is a powerful wallet accounts system and extendable platform that you can use to create secure sub-wallets sitting on top of a funding source such as LND or CLN (installed separately from the marketplace). This allows you to be an "Uncle Jim" to friends and family or to organize or limit access to funds. Extremely extensible, it can also provide a wide range of other features like connecting bolt cards or ATMs. See also [LNbits.com](https://LNbits.com/). - -## Getting Started -### The Lightning Network - -Bitcoin's Lightning Network is a second-layer scaling solution designed to enable faster and cheaper transactions on top of the Bitcoin blockchain. It allows users to create off-chain payment channels, which can facilitate multiple transactions without needing to record each one on the main blockchain. - -Before installing and using LNbits you should read and understand the extensive documentation for either [LND](https://github.com/Start9Labs/lnd-startos/blob/master/docs/instructions.md) or [CLN](https://github.com/Start9Labs/cln-startos/blob/master/docs/instructions.md), at least one of which you must install prior to using LNbits. - - -### First Use - -You'll need to first select an underlying Lightning Network implementation (see above) prior to starting the service for the first time. - -**NOTE:** If the LN implementation is changed after using LNBits this will delete all LNBits accounts and wallets related to the previously configured LN implementation! All LN funds will still be available on the underlying LN implementation. - -Once ready, and you start the service and launch the Web UI you will be prompted to set up a *Superuser account*. This is your principle admin account with full rights. As you are the administrator you may use the superuser account as you main account or you can later create additional accounts ranging from admin level to ordinary users. You can reset this password if you forget it in StartOS from `LNbits > Actions > Reset Password`. - -### Funding - -LNbits sits on top of your chosen Lightning Network funding source, but only has access to the funds you main available to it. This means if you have channels on your LN implementation with a total of 500,000 sats of liquidity on your side, you can add up to that amount on LNbits. You can do this in two ways:- - -1. Create an invoice in LNbits and pay yourself from the same wallet that is funding your LNbits instance -2. Manually choose to add funds by clicking on a wallet and then the "Credit/Debit" button. - -Adding 1000 sats to a wallet by either means does not alter the balance of the underling Lightning Network implementation. But should you create an invoice in LNbits and pay it from an outside source, again say 1000 sats, your underlying LN implementation would handle the process, liquidity on your LN implementation would increment by 1000 sats and the same 1000 sats would be credited to the LNbits wallet. - -**NOTE:** You could spend 100% of your liquidity on your LN implementation through some other integration method and have no funds to spend, yet LNbits and any users would be unaware. LNbits is merely an accounting system on top of your LN implementation. Remember: All wallets are ultimately bound by the capacity of your LN node. If one wallet is allocated 1000 sats but your underlying node only has 900 sats of outbound capacity, payments will simply fail. - - -## Backups - -When your server backs up LNbits, it takes a copy of your settings, your user accounts and their sub-wallets. It does NOT back up and funds, just the accounting for individual users. Actual funds remain on the underlying LN implementation (i.e. LND or CLN). - - -## Interacting with LNbits and connecting wallets - -LNbits wallets can be used as real wallets and have wallet software manage them. This is incredibly useful and powerful, allowing you to safely segregate funds or to only carry access to small amounts of your LN channel capacity in person. - -- [Alby Browser Extension](wallet-integrations/alby-extension.md) -- [BlueWallet](wallet-integrations/bluewallet.md) -- [Zeus](wallet-integrations/zeus.md) \ No newline at end of file diff --git a/docs/wallet-integrations/alby-extension.md b/docs/wallet-integrations/alby-extension.md deleted file mode 100644 index 0b0c6c6..0000000 --- a/docs/wallet-integrations/alby-extension.md +++ /dev/null @@ -1,27 +0,0 @@ -# Alby Browser Extension with LNbits - -Alby is a browser extension that can be connected to your lightning node a number of ways. This guide will go over connecting Alby to your **LNbits wallet** which allows allocation of funds. - - -**NOTE:** This guide assumes you have already setup LNbits as per [this guide](../instructions.md). - - -1. Download the Alby extension by visiting the [Alby Github](https://github.com/getAlby/lightning-browser-extension#installation), selecting your browser, and installing. - -1. Create a strong "unlock" password and store it somewhere safe, like your Vaultwarden password manager. - -1. On the next screen, in **Bring Your Own Wallet**, click **Find Your Wallet**. - -1. Click **StartOS** first, then **LNbits**. - -1. You will be asked to add the *LNbits Admin Key*. - -1. Head back to LNbits and select the wallet you want to use then click on the arrow to the right of **Node URL, API keys and API docs** to expand the details. - -1. Copy the **Admin key** and paste it into Alby. - -1. If you have opened the LNbits web UI via your preferred interface (IP, .local, clearnet) you can copy the Node URL from the same section of LNbits. Otherwise head back to your StartOS LNbits service page, the Interfaces section, and choose the one you prefer. - -1. Click **Continue**. Once the connection is completed you will see a success page that displays the balance of your LNbits wallet. - -You’re now setup with Alby and LNbits! diff --git a/docs/wallet-integrations/bluewallet.md b/docs/wallet-integrations/bluewallet.md deleted file mode 100644 index d7c123a..0000000 --- a/docs/wallet-integrations/bluewallet.md +++ /dev/null @@ -1,25 +0,0 @@ -# BlueWallet with LNbits - - -**WARNING:** This is not the same as connecting BlueWallet directly to your lightning node - using LNbits allows us to allocate a specific amount of funds to BlueWallet instead of giving it full access to your lightning node. We can also use LNbits to permit BlueWallet to **just receive** satoshis, or the ability to both **receive and spend** satoshis. - -**NOTE:** This guide assumes you have already setup LNbits as per [this guide](../instructions.md). - -**WARNING:** This will not work with CLN as your underlying LN implementation! - - -1. BlueWallet requires that we use the LndHub extension in order to connect to LNbits. To install this, click **Extensions**, in the **ALL** tab find LndHub, click **MANAGE**, find the latest version and click **INSTALL**. Once installed, click **ENABLE** - -1. Click **OPEN** *or* **LndHub** under *Extensions* - -1. Make sure the wallet you want to use is selected below the two QR codes, we will use these in a moment. - -1. Install [BlueWallet](https://bluewallet.io/) if you haven't already. - -1. Go to Import wallet on BlueWallet, then click on *Scan or import a file*. - -1. *If you only want this wallet to be able to RECEIVE PAYMENTS, scan the "Invoice" QR code* - *If you are happy for this wallet to be able to both receive and MAKE payments scan the "Admin" QR code* - - -Congratulations! BlueWallet is set up and ready to use lightning via your own lightning node - furthermore it will only be able to use your node in the way you allow it, via LNbits. \ No newline at end of file diff --git a/docs/wallet-integrations/zeus.md b/docs/wallet-integrations/zeus.md deleted file mode 100644 index 0af9bea..0000000 --- a/docs/wallet-integrations/zeus.md +++ /dev/null @@ -1,27 +0,0 @@ -# Zeus with LNbits - -**WARNING:** This is not the same as connecting Zeus directly to your lightning node - using LNbits allows us to allocate a specific amount of funds to Zeus instead of giving it full access to your lightning node. We can also use LNbits to permit Zeus to **just receive** satoshis, or the ability to both **receive and spend** satoshis. - -**NOTE:** This guide assumes you have already setup LNbits as per [this guide](../instructions.md). - -**WARNING:** This will not work with CLN as your underlying LN implementation! - - -1. Zeus requires that we use the LndHub extension in order to connect to LNbits. To install this, click **Extensions**, in the **ALL** tab find LndHub, click **MANAGE**, find the latest version and click **INSTALL**. Once installed, click **ENABLE** - -1. Click **OPEN** *or* **LndHub** under *Extensions* - -1. Make sure the wallet you want to use is selected below the two QR codes, we will use these in a moment. - - -1. Install [Zeus](https://zeusln.app/) if you haven't already. - -1. In Zeus, if you are using it for the first time, tap "Scan node config". Allow camera access, scan the QR code, and then tap 'Save node config'. Zeus will fill in your node details based on the information in the QR code. If you already have other nodes configured in Zeus, go to Settings > Connect a node > + . But before you scan anything... - -1. *If you only want this wallet to be able to RECEIVE PAYMENTS, scan the "Invoice" QR code* - *If you are happy for this wallet to be able to both receive and MAKE payments scan the "Admin" QR code* - -1. Once scanned, name the wallet if you wish, then hit **SAVE NODE CONFIG**. - - -Congratulations! Zeus is set up and ready to use lightning via your own lightning node - furthermore it will only be able to use your node in the way you allow it, via LNbits. diff --git a/s9pk.mk b/s9pk.mk index 9d887af..45ebfc9 100644 --- a/s9pk.mk +++ b/s9pk.mk @@ -77,6 +77,30 @@ install: | check-deps check-init 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) diff --git a/startos/manifest/index.ts b/startos/manifest/index.ts index d2edc89..1a94655 100644 --- a/startos/manifest/index.ts +++ b/startos/manifest/index.ts @@ -11,7 +11,7 @@ export const manifest = setupManifest({ marketingSite: 'https://lnbits.com/', donationUrl: 'https://demo.lnbits.com/tipjar/DwaUiE4kBX6mUW6pj3X5Kg', docsUrl: - 'https://github.com/Start9Labs/lnbits-startos/blob/master/instructions.md', + 'https://docs.lnbits.org/', description: { short, long }, volumes: ['main'], images: { From 3c472dff4e693fa4a922037fa0f835a1558f3b4b Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Wed, 4 Mar 2026 08:49:42 -0700 Subject: [PATCH 44/49] chore(lnbits): upgrade SDK from beta.48 to beta.55 - Bump version to 1.5.0:0-beta.0 - Rename manifest keys (packageRepo, marketingUrl, docsUrls) - Replace ts-matches with zod in env file model - Remove preInstall from versionGraph, add seedFiles init - Use envFile.merge with catch defaults instead of envDefaults object - Update LND healthCheck from 'primary' to 'lnd' - Make dependency descriptions and release notes i18n - Replace PNG icon with SVG from upstream --- Dockerfile | 2 +- icon.png | Bin 29369 -> 0 bytes icon.svg | 4 + package-lock.json | 38 ++-- package.json | 2 +- startos/actions/lightningImplementation.ts | 10 +- startos/actions/resetPassword.ts | 4 +- startos/dependencies.ts | 4 +- startos/fileModels/env.ts | 186 ++++++------------ startos/init/index.ts | 2 + startos/init/seedFiles.ts | 9 + startos/install/versionGraph.ts | 8 - startos/install/versions/index.ts | 2 +- ...{v1.4.2_1-beta.0.ts => v1.5.0_0-beta.0.ts} | 25 ++- startos/main.ts | 6 +- startos/manifest/i18n.ts | 24 +++ startos/manifest/index.ts | 27 +-- startos/utils.ts | 72 +------ 18 files changed, 172 insertions(+), 253 deletions(-) delete mode 100644 icon.png create mode 100644 icon.svg create mode 100644 startos/init/seedFiles.ts rename startos/install/versions/{v1.4.2_1-beta.0.ts => v1.5.0_0-beta.0.ts} (60%) diff --git a/Dockerfile b/Dockerfile index 83eda4c..889b926 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM lnbits/lnbits:v1.4.2 AS builder +FROM lnbits/lnbits:v1.5.0 AS builder # arm64 or amd64 ARG PLATFORM diff --git a/icon.png b/icon.png deleted file mode 100644 index a78e7df4dad3a17640ad82dbfe524f91bcd86005..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29369 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 diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..bf50d67 --- /dev/null +++ b/icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/package-lock.json b/package-lock.json index 88e55e6..a0e8e17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "lnbits-startos", "dependencies": { - "@start9labs/start-sdk": "0.4.0-beta.48" + "@start9labs/start-sdk": "0.4.0-beta.55" }, "devDependencies": { "@types/node": "^22.16.2", @@ -49,9 +49,9 @@ } }, "node_modules/@start9labs/start-sdk": { - "version": "0.4.0-beta.48", - "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.48.tgz", - "integrity": "sha512-beMdwhUffhnbSm3FgkWPJjAWazMhNMzqbHtS6yK2hX6VpP39JxzDVo8vEbJAUfot6LPw+OhOUtPEu6KDaGre6A==", + "version": "0.4.0-beta.55", + "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.55.tgz", + "integrity": "sha512-vPieBCBIhlSZDTtmDS8RDfC8gjJKaj2LtJThz/QGXN/NhFRUxUcVKBNL51S7MbOyJS7IYM/XU40Jqawdq2mqEw==", "license": "MIT", "dependencies": { "@iarna/toml": "^3.0.0", @@ -62,8 +62,9 @@ "ini": "^5.0.0", "isomorphic-fetch": "^3.0.0", "mime": "^4.0.7", - "ts-matches": "^6.3.2", - "yaml": "^2.7.1" + "yaml": "^2.7.1", + "zod": "^4.3.6", + "zod-deep-partial": "^1.2.0" } }, "node_modules/@types/ini": { @@ -186,12 +187,6 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, - "node_modules/ts-matches": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/ts-matches/-/ts-matches-6.5.0.tgz", - "integrity": "sha512-MhuobYhHYn6MlOTPAF/qk3tsRRioPac5ofYn68tc3rAJaGjsw1MsX1MOSep52DkvNJPgNV0F73zfgcQfYTVeyQ==", - "license": "MIT" - }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -249,6 +244,25 @@ "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", + "peer": true, + "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" + } } } } diff --git a/package.json b/package.json index 7b583a2..1b55527 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "check": "tsc --noEmit" }, "dependencies": { - "@start9labs/start-sdk": "0.4.0-beta.48" + "@start9labs/start-sdk": "0.4.0-beta.55" }, "devDependencies": { "@types/node": "^22.16.2", diff --git a/startos/actions/lightningImplementation.ts b/startos/actions/lightningImplementation.ts index e73d7f9..9cf7e81 100644 --- a/startos/actions/lightningImplementation.ts +++ b/startos/actions/lightningImplementation.ts @@ -24,7 +24,7 @@ export const setLnImplementation = sdk.Action.withInput( 'set-lightning-implementation', // metadata - async ({ effects }) => ({ + { name: i18n('Lightning Implementation'), description: i18n( 'Select the Lightning Implementation for LNbits to utilize', @@ -35,7 +35,7 @@ export const setLnImplementation = sdk.Action.withInput( allowedStatuses: 'any', group: null, visibility: 'enabled', - }), + }, // form input specification inputSpec, @@ -60,10 +60,12 @@ export const setLnImplementation = sdk.Action.withInput( .const(effects) try { - await access('/media/startos/volumes/main/database.sqlite3') + await access(sdk.volumes.main.subpath('database.sqlite3')) if (configuredLnImplementation !== input.implementation) { console.log( - i18n('Existing LN implementation does not match input. Resetting DB...'), + i18n( + 'Existing LN implementation does not match input. Resetting DB...', + ), ) await rm('/media/startos/volumes/main/database.sqlite3') } diff --git a/startos/actions/resetPassword.ts b/startos/actions/resetPassword.ts index ed2ca18..1d9737a 100644 --- a/startos/actions/resetPassword.ts +++ b/startos/actions/resetPassword.ts @@ -8,7 +8,7 @@ export const resetPassword = sdk.Action.withoutInput( 'reset-password', // metadata - async ({ effects }) => ({ + { name: i18n('Reset Password'), description: i18n( 'Reset Password for the super_user in the event of a lost or forgotten password', @@ -17,7 +17,7 @@ export const resetPassword = sdk.Action.withoutInput( allowedStatuses: 'only-running', group: null, visibility: 'enabled', - }), + }, // the execution function async ({ effects }) => { diff --git a/startos/dependencies.ts b/startos/dependencies.ts index 5ace8cf..3caf13c 100644 --- a/startos/dependencies.ts +++ b/startos/dependencies.ts @@ -9,9 +9,9 @@ export const setDependencies = sdk.setupDependencies(async ({ effects }) => { if (configuredLnImplementation === 'LndRestWallet') { return { lnd: { - healthChecks: ['primary'], + healthChecks: ['lnd'], kind: 'running', - versionRange: '>=0.20.0-beta:1-beta.2', + versionRange: '>=28.3:0-beta.0', }, } } else if (configuredLnImplementation === 'CoreLightningWallet') { diff --git a/startos/fileModels/env.ts b/startos/fileModels/env.ts index 22a1871..2646927 100644 --- a/startos/fileModels/env.ts +++ b/startos/fileModels/env.ts @@ -1,131 +1,59 @@ -import { matches, FileHelper } from '@start9labs/start-sdk' -const { object, string } = matches - -import { envDefaults } from '../utils' -import { literal, literals } from 'ts-matches' - -const { - HOST, - PORT, - FORWARDED_ALLOW_IPS, - DEBUG, - AUTH_ALLOWED_METHODS, - LNBITS_ALLOWED_USERS, - LNBITS_ADMIN_USERS, - LNBITS_ADMIN_EXTENSIONS, - LNBITS_ADMIN_UI, - LNBITS_DEFAULT_WALLET_NAME, - LNBITS_HIDE_API, - LNBITS_DISABLED_EXTENSIONS, - LNBITS_DATA_FOLDER, - LNBITS_FORCE_HTTPS, - LNBITS_RESERVE_FEE_MIN, - LNBITS_RESERVE_FEE_PERCENT, - LNBITS_SITE_TITLE, - LNBITS_SITE_TAGLINE, - LNBITS_SITE_DESCRIPTION, - LNBITS_THEME_OPTIONS, - LNBITS_CUSTOM_LOGO, - LNBITS_BACKEND_WALLET_CLASS, - LNBITS_ALLOWED_FUNDING_SOURCES, - CLIGHTNING_RPC, - LND_REST_ENDPOINT, - LND_REST_CERT, - LND_REST_MACAROON, -} = envDefaults - -const shape = object({ - HOST: literal(HOST).onMismatch(HOST), - PORT: literal(PORT).onMismatch(PORT), - - // uvicorn variable, allow https behind a proxy - FORWARDED_ALLOW_IPS: - literal(FORWARDED_ALLOW_IPS).onMismatch(FORWARDED_ALLOW_IPS), - - DEBUG: literals('true', 'false').optional().onMismatch(DEBUG), - - AUTH_ALLOWED_METHODS: string.onMismatch(AUTH_ALLOWED_METHODS), - - LNBITS_ALLOWED_USERS: string.optional().onMismatch(LNBITS_ALLOWED_USERS), - LNBITS_ADMIN_USERS: string.optional().onMismatch(LNBITS_ADMIN_USERS), - // Extensions only admin can access - LNBITS_ADMIN_EXTENSIONS: string - .optional() - .onMismatch(LNBITS_ADMIN_EXTENSIONS), - // Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available - LNBITS_ADMIN_UI: literals('true', 'false') - .optional() - .onMismatch(LNBITS_ADMIN_UI), - - LNBITS_DEFAULT_WALLET_NAME: string - .optional() - .onMismatch(LNBITS_DEFAULT_WALLET_NAME), - - // Ad space description - // LNBITS_AD_SPACE_TITLE="Supported by" - // csv ad space, format ";;, ;;", extensions can choose to honor - // LNBITS_AD_SPACE="" # csv ad image filepaths or urls, extensions can choose to honor - LNBITS_HIDE_API: literals('true', 'false') - .optional() - .onMismatch(LNBITS_HIDE_API), // Hides wallet api, extensions can choose to honor - - // Disable extensions for all users, use "all" to disable all extensions - LNBITS_DISABLED_EXTENSIONS: string - .optional() - .onMismatch(LNBITS_DISABLED_EXTENSIONS), - - // Database: to use SQLite, specify LNBITS_DATA_FOLDER - // to use PostgreSQL, specify LNBITS_DATABASE_URL=postgres://... - // to use CockroachDB, specify LNBITS_DATABASE_URL=cockroachdb://... - // for both PostgreSQL and CockroachDB, you'll need to install - // psycopg2 as an additional dependency - LNBITS_DATA_FOLDER: - literal(LNBITS_DATA_FOLDER).onMismatch(LNBITS_DATA_FOLDER), - // LNBITS_DATABASE_URL="postgres://user:password@host:port/databasename" - - LNBITS_FORCE_HTTPS: literals('true', 'false') - .optional() - .onMismatch(LNBITS_FORCE_HTTPS), - // LNBITS_SERVICE_FEE="0.0" - // value in millisats - LNBITS_RESERVE_FEE_MIN: string.optional().onMismatch(LNBITS_RESERVE_FEE_MIN), - // value in percent - LNBITS_RESERVE_FEE_PERCENT: string - .optional() - .onMismatch(LNBITS_RESERVE_FEE_PERCENT), - - // Change theme - LNBITS_SITE_TITLE: string.optional().onMismatch(LNBITS_SITE_TITLE), - LNBITS_SITE_TAGLINE: string.optional().onMismatch(LNBITS_SITE_TAGLINE), - LNBITS_SITE_DESCRIPTION: string - .optional() - .onMismatch(LNBITS_SITE_DESCRIPTION), - // Choose from mint, flamingo, freedom, salvador, autumn, monochrome, classic - LNBITS_THEME_OPTIONS: string.optional().onMismatch(LNBITS_THEME_OPTIONS), - LNBITS_CUSTOM_LOGO: string.optional().onMismatch(LNBITS_CUSTOM_LOGO), - - // Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet, LnTipsWallet - // LndRestWallet, CoreLightningWallet, LNbitsWallet, SparkWallet, FakeWallet, EclairWallet - LNBITS_BACKEND_WALLET_CLASS: literals( - 'VoidWallet', - 'LndRestWallet', - 'CoreLightningWallet', - ).onMismatch(LNBITS_BACKEND_WALLET_CLASS), - // VoidWallet is just a fallback that works without any actual Lightning capabilities, - // just so you can see the UI before dealing with this file. - - LNBITS_ALLOWED_FUNDING_SOURCES: literals( - 'LndRestWallet', - 'CoreLightningWallet', - ).onMismatch(LNBITS_ALLOWED_FUNDING_SOURCES), - - // CLightningWallet - CLIGHTNING_RPC: literal(CLIGHTNING_RPC).onMismatch(CLIGHTNING_RPC), - - // LndRestWallet - LND_REST_ENDPOINT: literal(LND_REST_ENDPOINT).onMismatch(LND_REST_ENDPOINT), - LND_REST_CERT: literal(LND_REST_CERT).onMismatch(LND_REST_CERT), - LND_REST_MACAROON: literal(LND_REST_MACAROON).onMismatch(LND_REST_MACAROON), +import { FileHelper, z } from '@start9labs/start-sdk' +import { sdk } from '../sdk' +import { clnMountpoint, lndMountpoint } from '../utils' + +export const shape = z.object({ + HOST: z.literal('lnbits.startos').catch('lnbits.startos'), + PORT: z.literal('5000').catch('5000'), + FORWARDED_ALLOW_IPS: z.literal('*').catch('*'), + DEBUG: z.enum(['true', 'false']).optional().catch('false'), + AUTH_ALLOWED_METHODS: z.string().catch('username-password'), + LNBITS_ALLOWED_USERS: z.string().optional().catch(''), + LNBITS_ADMIN_USERS: z.string().optional().catch(''), + LNBITS_ADMIN_EXTENSIONS: z.string().optional().catch('ngrok, admin'), + LNBITS_ADMIN_UI: z.enum(['true', 'false']).optional().catch('true'), + LNBITS_DEFAULT_WALLET_NAME: z.string().optional().catch('LNbits wallet'), + LNBITS_HIDE_API: z.enum(['true', 'false']).optional().catch('false'), + LNBITS_DISABLED_EXTENSIONS: z.string().optional().catch('amilk'), + LNBITS_DATA_FOLDER: z.literal('./data').catch('./data'), + LNBITS_FORCE_HTTPS: z.enum(['true', 'false']).optional().catch('false'), + LNBITS_RESERVE_FEE_MIN: z.string().optional().catch('2000'), + LNBITS_RESERVE_FEE_PERCENT: z.string().optional().catch('1.0'), + LNBITS_SITE_TITLE: z.string().optional().catch('LNbits'), + LNBITS_SITE_TAGLINE: z + .string() + .optional() + .catch('free and open-source self-hosted lightning wallet'), + LNBITS_SITE_DESCRIPTION: z + .string() + .optional() + .catch('Made for you, hosted by you.'), + LNBITS_THEME_OPTIONS: z + .string() + .optional() + .catch('classic, bitcoin, freedom, mint, autumn, monochrome, salvador'), + LNBITS_CUSTOM_LOGO: z.string().optional().catch(''), + LNBITS_BACKEND_WALLET_CLASS: z + .enum(['VoidWallet', 'LndRestWallet', 'CoreLightningWallet']) + .catch('LndRestWallet'), + LNBITS_ALLOWED_FUNDING_SOURCES: z + .enum(['LndRestWallet', 'CoreLightningWallet']) + .catch('LndRestWallet'), + CLIGHTNING_RPC: z + .literal(`${clnMountpoint}/bitcoin/lightning-rpc`) + .catch(`${clnMountpoint}/bitcoin/lightning-rpc`), + LND_REST_ENDPOINT: z + .literal('https://lnd.startos:8080/') + .catch('https://lnd.startos:8080/'), + LND_REST_CERT: z + .literal(`${lndMountpoint}/tls.cert`) + .catch(`${lndMountpoint}/tls.cert`), + LND_REST_MACAROON: z + .literal(`${lndMountpoint}/data/chain/bitcoin/mainnet/admin.macaroon`) + .catch(`${lndMountpoint}/data/chain/bitcoin/mainnet/admin.macaroon`), }) -export const envFile = FileHelper.env('/media/startos/volumes/main/.env', shape) +export const envFile = FileHelper.env( + { base: sdk.volumes.main, subpath: '/.env' }, + shape, +) diff --git a/startos/init/index.ts b/startos/init/index.ts index 13eda04..44fe663 100644 --- a/startos/init/index.ts +++ b/startos/init/index.ts @@ -4,9 +4,11 @@ import { setInterfaces } from '../interfaces' import { versionGraph } from '../install/versionGraph' import { actions } from '../actions' import { restoreInit } from '../backups' +import { seedFiles } from './seedFiles' import { taskSetLnImplementation } from './taskSetLnImplementation' export const init = sdk.setupInit( + seedFiles, restoreInit, versionGraph, setInterfaces, diff --git a/startos/init/seedFiles.ts b/startos/init/seedFiles.ts new file mode 100644 index 0000000..694f151 --- /dev/null +++ b/startos/init/seedFiles.ts @@ -0,0 +1,9 @@ +import { envFile } from '../fileModels/env' +import { sdk } from '../sdk' + +export const seedFiles = sdk.setupOnInit(async (effects, kind) => { + if (kind !== 'install') return + await envFile.merge(effects, { + LNBITS_BACKEND_WALLET_CLASS: 'VoidWallet', + }) +}) diff --git a/startos/install/versionGraph.ts b/startos/install/versionGraph.ts index e87288c..5a4fad7 100644 --- a/startos/install/versionGraph.ts +++ b/startos/install/versionGraph.ts @@ -1,15 +1,7 @@ import { VersionGraph } from '@start9labs/start-sdk' import { current, other } from './versions' -import { envFile } from '../fileModels/env' -import { envDefaults } from '../utils' export const versionGraph = VersionGraph.of({ current, other, - preInstall: async (effects) => { - await envFile.write(effects, { - ...envDefaults, - LNBITS_BACKEND_WALLET_CLASS: 'VoidWallet', - }) - }, }) diff --git a/startos/install/versions/index.ts b/startos/install/versions/index.ts index 72d9ef2..0bb157a 100644 --- a/startos/install/versions/index.ts +++ b/startos/install/versions/index.ts @@ -1,3 +1,3 @@ -export { v1_4_2_1 as current } from './v1.4.2_1-beta.0' +export { v1_5_0_0 as current } from './v1.5.0_0-beta.0' export const other = [] diff --git a/startos/install/versions/v1.4.2_1-beta.0.ts b/startos/install/versions/v1.5.0_0-beta.0.ts similarity index 60% rename from startos/install/versions/v1.4.2_1-beta.0.ts rename to startos/install/versions/v1.5.0_0-beta.0.ts index f60bc0d..1fa2333 100644 --- a/startos/install/versions/v1.4.2_1-beta.0.ts +++ b/startos/install/versions/v1.5.0_0-beta.0.ts @@ -1,11 +1,17 @@ import { VersionInfo, IMPOSSIBLE, YAML } from '@start9labs/start-sdk' import { envFile } from '../../fileModels/env' -import { envDefaults } from '../../utils' import { readFile, rm } from 'fs/promises' +import { sdk } from '../../sdk' -export const v1_4_2_1 = VersionInfo.of({ - version: '1.4.2:1-beta.0', - releaseNotes: 'Revamped for StartOS 0.4.0', +export const v1_5_0_0 = VersionInfo.of({ + version: '1.5.0:0-beta.0', + releaseNotes: { + en_US: 'Revamped for StartOS 0.4.0', + es_ES: 'Renovado para StartOS 0.4.0', + de_DE: 'Überarbeitet für StartOS 0.4.0', + pl_PL: 'Przeprojektowany dla StartOS 0.4.0', + fr_FR: 'Refait pour StartOS 0.4.0', + }, migrations: { up: async ({ effects }) => { // get old config.yaml @@ -13,10 +19,10 @@ export const v1_4_2_1 = VersionInfo.of({ | { implementation: 'LndRestWallet' | 'CLightningWallet' } - | undefined = await readFile( - '/media/startos/volumes/main/start9/config.yaml', - 'utf-8', - ).then(YAML.parse, () => undefined) + | undefined = await sdk.volumes.main + .readFile('start9/config.yaml', 'utf-8') + .then((c) => c.toString('utf-8')) + .then(YAML.parse, () => undefined) if (configYaml) { const configuredImplementation = @@ -24,8 +30,7 @@ export const v1_4_2_1 = VersionInfo.of({ ? 'CoreLightningWallet' : 'LndRestWallet' - await envFile.write(effects, { - ...envDefaults, + await envFile.merge(effects, { LNBITS_BACKEND_WALLET_CLASS: configuredImplementation, LNBITS_ALLOWED_FUNDING_SOURCES: configuredImplementation, }) diff --git a/startos/main.ts b/startos/main.ts index 368ea85..46ee8bc 100644 --- a/startos/main.ts +++ b/startos/main.ts @@ -1,7 +1,7 @@ import { envFile } from './fileModels/env' import { i18n } from './i18n' import { sdk } from './sdk' -import { mainMounts, uiPort } from './utils' +import { clnMountpoint, lndMountpoint, mainMounts, uiPort } from './utils' export const main = sdk.setupMain(async ({ effects }) => { /** @@ -23,8 +23,8 @@ export const main = sdk.setupMain(async ({ effects }) => { configuredLnImplementation === 'LndRestWallet' ? 'lnd' : 'c-lightning', mountpoint: configuredLnImplementation === 'LndRestWallet' - ? '/mnt/lnd' - : '/mnt/cln', + ? lndMountpoint + : clnMountpoint, readonly: true, subpath: null, volumeId: 'main', diff --git a/startos/manifest/i18n.ts b/startos/manifest/i18n.ts index d02253c..3653ee2 100644 --- a/startos/manifest/i18n.ts +++ b/startos/manifest/i18n.ts @@ -19,6 +19,18 @@ export const depClnTitle = { fr_FR: 'Core Lightning', } +export const depClnDescription = { + en_US: 'Optionally connect LNbits to your CLN node.', + es_ES: + 'Opcionalmente conecte LNbits a su nodo CLN.', + de_DE: + 'Optional LNbits mit Ihrem CLN-Knoten verbinden.', + pl_PL: + 'Opcjonalnie połącz LNbits ze swoim węzłem CLN.', + fr_FR: + 'Connectez optionnellement LNbits à votre nœud CLN.', +} + export const depLndTitle = { en_US: 'LND', es_ES: 'LND', @@ -27,6 +39,18 @@ export const depLndTitle = { fr_FR: 'LND', } +export const depLndDescription = { + en_US: 'Optionally connect LNbits to your LND node.', + es_ES: + 'Opcionalmente conecte LNbits a su nodo LND.', + de_DE: + 'Optional LNbits mit Ihrem LND-Knoten verbinden.', + pl_PL: + 'Opcjonalnie połącz LNbits ze swoim węzłem LND.', + fr_FR: + 'Connectez optionnellement LNbits à votre nœud LND.', +} + export const long = { en_US: 'A very simple Python server that sits on top of any funding source, and can be used as an accounts system, extendable platform, development stack, fallback wallet or even instant wallet for LN demonstrations', diff --git a/startos/manifest/index.ts b/startos/manifest/index.ts index 1a94655..94c1d4c 100644 --- a/startos/manifest/index.ts +++ b/startos/manifest/index.ts @@ -1,17 +1,22 @@ import { setupManifest } from '@start9labs/start-sdk' -import { short, long, depClnTitle, depLndTitle } from './i18n' +import { + short, + long, + depClnTitle, + depClnDescription, + depLndTitle, + depLndDescription, +} from './i18n' export const manifest = setupManifest({ id: 'lnbits', title: 'LNbits', license: 'mit', - wrapperRepo: 'https://github.com/Start9Labs/lnbits-startos', + packageRepo: 'https://github.com/Start9Labs/lnbits-startos', upstreamRepo: 'https://github.com/lnbits/lnbits', - supportSite: 'https://github.com/lnbits/lnbits/issues', - marketingSite: 'https://lnbits.com/', + marketingUrl: 'https://lnbits.com/', donationUrl: 'https://demo.lnbits.com/tipjar/DwaUiE4kBX6mUW6pj3X5Kg', - docsUrl: - 'https://docs.lnbits.org/', + docsUrls: ['https://docs.lnbits.org/'], description: { short, long }, volumes: ['main'], images: { @@ -20,24 +25,24 @@ export const manifest = setupManifest({ dockerBuild: {}, }, arch: ['aarch64', 'x86_64'], - emulateMissingAs: 'aarch64' + emulateMissingAs: 'aarch64', }, }, dependencies: { 'c-lightning': { - description: 'Optionally connect RTL to your CLN node.', + description: depClnDescription, optional: true, metadata: { title: depClnTitle, - icon: 'https://github.com/Start9Labs/cln-startos/blob/master/icon.png?raw=true', + icon: 'https://raw.githubusercontent.com/Start9Labs/cln-startos/refs/heads/update/040/icon.svg', }, }, lnd: { - description: 'Optionally connect RTL to your LND node.', + description: depLndDescription, optional: true, metadata: { title: depLndTitle, - icon: 'https://github.com/Start9Labs/lnd-startos/blob/master/icon.png?raw=true', + icon: 'https://raw.githubusercontent.com/Start9Labs/lnd-startos/refs/heads/update/040/icon.svg', }, }, }, diff --git a/startos/utils.ts b/startos/utils.ts index 4eb8fa9..0dde8c9 100644 --- a/startos/utils.ts +++ b/startos/utils.ts @@ -7,6 +7,9 @@ export const randomPassword = { len: 22, } +export const lndMountpoint = '/mnt/lnd' +export const clnMountpoint = '/mnt/cln' + export const mainMounts = sdk.Mounts.of().mountVolume({ volumeId: 'main', mountpoint: '/app/data', @@ -14,72 +17,3 @@ export const mainMounts = sdk.Mounts.of().mountVolume({ subpath: null, type: 'directory', }) - -export const envDefaults = { - HOST: 'lnbits.startos', - PORT: '5000', - - // uvicorn variable, allow https behind a proxy - FORWARDED_ALLOW_IPS: '*', - - DEBUG: 'false', - - AUTH_ALLOWED_METHODS: 'username-password', - LNBITS_ALLOWED_USERS: '', - LNBITS_ADMIN_USERS: '', - // Extensions only admin can access - LNBITS_ADMIN_EXTENSIONS: 'ngrok, admin', - // Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available - LNBITS_ADMIN_UI: 'true', - - LNBITS_DEFAULT_WALLET_NAME: 'LNbits wallet', - - // Ad space description - // LNBITS_AD_SPACE_TITLE="Supported by" - // csv ad space, format ";;, ;;", extensions can choose to honor - // LNBITS_AD_SPACE="" # csv ad image filepaths or urls, extensions can choose to honor - LNBITS_HIDE_API: 'false', // Hides wallet api, extensions can choose to honor - - // Disable extensions for all users, use "all" to disable all extensions - LNBITS_DISABLED_EXTENSIONS: 'amilk', - - // Database: to use SQLite, specify LNBITS_DATA_FOLDER - // to use PostgreSQL, specify LNBITS_DATABASE_URL=postgres://... - // to use CockroachDB, specify LNBITS_DATABASE_URL=cockroachdb://... - // for both PostgreSQL and CockroachDB, you'll need to install - // psycopg2 as an additional dependency - LNBITS_DATA_FOLDER: './data', - // LNBITS_DATABASE_URL="postgres://user:password@host:port/databasename" - - LNBITS_FORCE_HTTPS: 'false', - // LNBITS_SERVICE_FEE="0.0" - // value in millisats - LNBITS_RESERVE_FEE_MIN: '2000', - // value in percent - LNBITS_RESERVE_FEE_PERCENT: '1.0', - - // Change theme - LNBITS_SITE_TITLE: 'LNbits', - LNBITS_SITE_TAGLINE: 'free and open-source self-hosted lightning wallet', - LNBITS_SITE_DESCRIPTION: 'Made for you, hosted by you.', - // Choose from mint, flamingo, freedom, salvador, autumn, monochrome, classic - LNBITS_THEME_OPTIONS: - 'classic, bitcoin, freedom, mint, autumn, monochrome, salvador', - LNBITS_CUSTOM_LOGO: '', - - // Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet, LnTipsWallet - // LndRestWallet, CoreLightningWallet, LNbitsWallet, SparkWallet, FakeWallet, EclairWallet - LNBITS_BACKEND_WALLET_CLASS: 'LndRestWallet', - // VoidWallet is just a fallback that works without any actual Lightning capabilities, - // just so you can see the UI before dealing with this file. - - LNBITS_ALLOWED_FUNDING_SOURCES: 'LndRestWallet', - - // CLightningWallet - CLIGHTNING_RPC: '/mnt/cln/bitcoin/lightning-rpc', - - // LndRestWallet - LND_REST_ENDPOINT: 'https://lnd.startos:8080/', - LND_REST_CERT: '/mnt/lnd/tls.cert', - LND_REST_MACAROON: '/mnt/lnd/data/chain/bitcoin/mainnet/admin.macaroon', -} as const From 4564636764c1390642ea006450c1875c9edc1329 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Wed, 4 Mar 2026 09:27:52 -0700 Subject: [PATCH 45/49] some fixes --- startos/actions/resetPassword.ts | 11 +++-- startos/dependencies.ts | 6 +-- startos/init/seedFiles.ts | 1 + startos/install/versions/v1.5.0_0-beta.0.ts | 4 +- startos/manifest/i18n.ts | 48 +++++---------------- startos/manifest/index.ts | 16 ++----- startos/utils.ts | 4 -- 7 files changed, 28 insertions(+), 62 deletions(-) diff --git a/startos/actions/resetPassword.ts b/startos/actions/resetPassword.ts index 1d9737a..2b9611e 100644 --- a/startos/actions/resetPassword.ts +++ b/startos/actions/resetPassword.ts @@ -1,7 +1,7 @@ +import { utils } from '@start9labs/start-sdk' import { i18n } from '../i18n' import { sdk } from '../sdk' -import { db, mainMounts, randomPassword } from '../utils' -import { utils } from '@start9labs/start-sdk' +import { db, mainMounts } from '../utils' export const resetPassword = sdk.Action.withoutInput( // id @@ -21,8 +21,11 @@ export const resetPassword = sdk.Action.withoutInput( // the execution function async ({ effects }) => { - const newPassword = utils.getDefaultString(randomPassword) - const res = await sdk.SubContainer.withTemp( + const newPassword = utils.getDefaultString({ + charset: 'a-z,A-Z,1-9,+,/', + len: 22, + }) + await sdk.SubContainer.withTemp( effects, { imageId: 'lnbits' }, mainMounts, diff --git a/startos/dependencies.ts b/startos/dependencies.ts index 3caf13c..dc9047a 100644 --- a/startos/dependencies.ts +++ b/startos/dependencies.ts @@ -9,9 +9,9 @@ export const setDependencies = sdk.setupDependencies(async ({ effects }) => { if (configuredLnImplementation === 'LndRestWallet') { return { lnd: { - healthChecks: ['lnd'], kind: 'running', - versionRange: '>=28.3:0-beta.0', + versionRange: '>=0.20.0-beta:2-beta.0', + healthChecks: ['lnd'], }, } } else if (configuredLnImplementation === 'CoreLightningWallet') { @@ -19,7 +19,7 @@ export const setDependencies = sdk.setupDependencies(async ({ effects }) => { 'c-lightning': { healthChecks: ['lightningd'], kind: 'running', - versionRange: '>=25.12:1-beta.1', + versionRange: '>=25.12.1:2-beta.0', }, } } diff --git a/startos/init/seedFiles.ts b/startos/init/seedFiles.ts index 694f151..794ec5a 100644 --- a/startos/init/seedFiles.ts +++ b/startos/init/seedFiles.ts @@ -3,6 +3,7 @@ import { sdk } from '../sdk' export const seedFiles = sdk.setupOnInit(async (effects, kind) => { if (kind !== 'install') return + await envFile.merge(effects, { LNBITS_BACKEND_WALLET_CLASS: 'VoidWallet', }) diff --git a/startos/install/versions/v1.5.0_0-beta.0.ts b/startos/install/versions/v1.5.0_0-beta.0.ts index 1fa2333..d1c1b14 100644 --- a/startos/install/versions/v1.5.0_0-beta.0.ts +++ b/startos/install/versions/v1.5.0_0-beta.0.ts @@ -1,6 +1,6 @@ -import { VersionInfo, IMPOSSIBLE, YAML } from '@start9labs/start-sdk' +import { IMPOSSIBLE, VersionInfo, YAML } from '@start9labs/start-sdk' +import { rm } from 'fs/promises' import { envFile } from '../../fileModels/env' -import { readFile, rm } from 'fs/promises' import { sdk } from '../../sdk' export const v1_5_0_0 = VersionInfo.of({ diff --git a/startos/manifest/i18n.ts b/startos/manifest/i18n.ts index 3653ee2..bb8e7c2 100644 --- a/startos/manifest/i18n.ts +++ b/startos/manifest/i18n.ts @@ -1,54 +1,28 @@ export const short = { - en_US: - 'Free and open-source lightning-network wallet/accounts system.', + en_US: 'Free and open-source lightning-network wallet/accounts system.', es_ES: 'Sistema de billetera/cuentas de red Lightning gratuito y de código abierto.', de_DE: 'Kostenloses und quelloffenes Lightning-Netzwerk-Wallet-/Kontensystem.', - pl_PL: - 'Darmowy i otwartoźródłowy system portfela/kont sieci Lightning.', + pl_PL: 'Darmowy i otwartoźródłowy system portfela/kont sieci Lightning.', fr_FR: 'Système de portefeuille/comptes du réseau Lightning gratuit et open-source.', } -export const depClnTitle = { - en_US: 'Core Lightning', - es_ES: 'Core Lightning', - de_DE: 'Core Lightning', - pl_PL: 'Core Lightning', - fr_FR: 'Core Lightning', -} - export const depClnDescription = { en_US: 'Optionally connect LNbits to your CLN node.', - es_ES: - 'Opcionalmente conecte LNbits a su nodo CLN.', - de_DE: - 'Optional LNbits mit Ihrem CLN-Knoten verbinden.', - pl_PL: - 'Opcjonalnie połącz LNbits ze swoim węzłem CLN.', - fr_FR: - 'Connectez optionnellement LNbits à votre nœud CLN.', -} - -export const depLndTitle = { - en_US: 'LND', - es_ES: 'LND', - de_DE: 'LND', - pl_PL: 'LND', - fr_FR: 'LND', + es_ES: 'Opcionalmente conecte LNbits a su nodo CLN.', + de_DE: 'Optional LNbits mit Ihrem CLN-Knoten verbinden.', + pl_PL: 'Opcjonalnie połącz LNbits ze swoim węzłem CLN.', + fr_FR: 'Connectez optionnellement LNbits à votre nœud CLN.', } export const depLndDescription = { en_US: 'Optionally connect LNbits to your LND node.', - es_ES: - 'Opcionalmente conecte LNbits a su nodo LND.', - de_DE: - 'Optional LNbits mit Ihrem LND-Knoten verbinden.', - pl_PL: - 'Opcjonalnie połącz LNbits ze swoim węzłem LND.', - fr_FR: - 'Connectez optionnellement LNbits à votre nœud LND.', + es_ES: 'Opcionalmente conecte LNbits a su nodo LND.', + de_DE: 'Optional LNbits mit Ihrem LND-Knoten verbinden.', + pl_PL: 'Opcjonalnie połącz LNbits ze swoim węzłem LND.', + fr_FR: 'Connectez optionnellement LNbits à votre nœud LND.', } export const long = { @@ -62,4 +36,4 @@ export const long = { 'Bardzo prosty serwer Python, który działa na dowolnym źródle finansowania i może być używany jako system kont, rozszerzalna platforma, stos deweloperski, portfel zapasowy lub nawet portfel natychmiastowy do demonstracji LN', fr_FR: "Un serveur Python très simple qui fonctionne au-dessus de n'importe quelle source de financement, et peut être utilisé comme système de comptes, plateforme extensible, pile de développement, portefeuille de secours ou même portefeuille instantané pour les démonstrations LN", -} \ No newline at end of file +} diff --git a/startos/manifest/index.ts b/startos/manifest/index.ts index 94c1d4c..eace889 100644 --- a/startos/manifest/index.ts +++ b/startos/manifest/index.ts @@ -1,18 +1,11 @@ import { setupManifest } from '@start9labs/start-sdk' -import { - short, - long, - depClnTitle, - depClnDescription, - depLndTitle, - depLndDescription, -} from './i18n' +import { depClnDescription, depLndDescription, long, short } from './i18n' export const manifest = setupManifest({ id: 'lnbits', title: 'LNbits', license: 'mit', - packageRepo: 'https://github.com/Start9Labs/lnbits-startos', + packageRepo: 'https://github.com/Start9Labs/lnbits-startos/tree/update/040', upstreamRepo: 'https://github.com/lnbits/lnbits', marketingUrl: 'https://lnbits.com/', donationUrl: 'https://demo.lnbits.com/tipjar/DwaUiE4kBX6mUW6pj3X5Kg', @@ -25,7 +18,6 @@ export const manifest = setupManifest({ dockerBuild: {}, }, arch: ['aarch64', 'x86_64'], - emulateMissingAs: 'aarch64', }, }, dependencies: { @@ -33,7 +25,7 @@ export const manifest = setupManifest({ description: depClnDescription, optional: true, metadata: { - title: depClnTitle, + title: 'Core Lightning', icon: 'https://raw.githubusercontent.com/Start9Labs/cln-startos/refs/heads/update/040/icon.svg', }, }, @@ -41,7 +33,7 @@ export const manifest = setupManifest({ description: depLndDescription, optional: true, metadata: { - title: depLndTitle, + title: 'LND', icon: 'https://raw.githubusercontent.com/Start9Labs/lnd-startos/refs/heads/update/040/icon.svg', }, }, diff --git a/startos/utils.ts b/startos/utils.ts index 0dde8c9..3d00801 100644 --- a/startos/utils.ts +++ b/startos/utils.ts @@ -2,10 +2,6 @@ import { sdk } from './sdk' export const uiPort = 5000 export const db = '/app/data/database.sqlite3' -export const randomPassword = { - charset: 'a-z,A-Z,1-9,+,/', - len: 22, -} export const lndMountpoint = '/mnt/lnd' export const clnMountpoint = '/mnt/cln' From 13761b7e1f27ae4460c80765d8d0da8c4f9bd0d1 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Thu, 5 Mar 2026 23:31:15 -0700 Subject: [PATCH 46/49] Update start-sdk to beta.57 Co-Authored-By: Claude Opus 4.6 --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index a0e8e17..878acd9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "lnbits-startos", "dependencies": { - "@start9labs/start-sdk": "0.4.0-beta.55" + "@start9labs/start-sdk": "0.4.0-beta.57" }, "devDependencies": { "@types/node": "^22.16.2", @@ -49,9 +49,9 @@ } }, "node_modules/@start9labs/start-sdk": { - "version": "0.4.0-beta.55", - "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.55.tgz", - "integrity": "sha512-vPieBCBIhlSZDTtmDS8RDfC8gjJKaj2LtJThz/QGXN/NhFRUxUcVKBNL51S7MbOyJS7IYM/XU40Jqawdq2mqEw==", + "version": "0.4.0-beta.57", + "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.57.tgz", + "integrity": "sha512-hmyRUk0NlaMn60hxgfKBL8Ua64A9KqRpZQQw4VaBKIddaRGhGYN9raNS/0LpzJ4eSegTMBBqb+tkGYmPdNExcA==", "license": "MIT", "dependencies": { "@iarna/toml": "^3.0.0", @@ -74,9 +74,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.19.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.8.tgz", - "integrity": "sha512-ebO/Yl+EAvVe8DnMfi+iaAyIqYdK0q/q0y0rw82INWEKJOBe6b/P3YWE8NW7oOlF/nXFNrHwhARrN/hdgDkraA==", + "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": { diff --git a/package.json b/package.json index 1b55527..a14093c 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "check": "tsc --noEmit" }, "dependencies": { - "@start9labs/start-sdk": "0.4.0-beta.55" + "@start9labs/start-sdk": "0.4.0-beta.57" }, "devDependencies": { "@types/node": "^22.16.2", From e3aa2043b9b589fae4f681d47c3b0c376b0e3f9c Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Fri, 6 Mar 2026 00:19:36 -0700 Subject: [PATCH 47/49] Update start-sdk to beta.58 Co-Authored-By: Claude Opus 4.6 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 878acd9..c78b5c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "lnbits-startos", "dependencies": { - "@start9labs/start-sdk": "0.4.0-beta.57" + "@start9labs/start-sdk": "0.4.0-beta.58" }, "devDependencies": { "@types/node": "^22.16.2", @@ -49,9 +49,9 @@ } }, "node_modules/@start9labs/start-sdk": { - "version": "0.4.0-beta.57", - "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.57.tgz", - "integrity": "sha512-hmyRUk0NlaMn60hxgfKBL8Ua64A9KqRpZQQw4VaBKIddaRGhGYN9raNS/0LpzJ4eSegTMBBqb+tkGYmPdNExcA==", + "version": "0.4.0-beta.58", + "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.58.tgz", + "integrity": "sha512-d8R+g9WNpdxvvPyBDL2VGVJjmhleIV0JEOKm28JrhnfmNSf9gOLY40QzUV1PuSCNXl50dYiom/SXab1q0faidw==", "license": "MIT", "dependencies": { "@iarna/toml": "^3.0.0", diff --git a/package.json b/package.json index a14093c..964470a 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "check": "tsc --noEmit" }, "dependencies": { - "@start9labs/start-sdk": "0.4.0-beta.57" + "@start9labs/start-sdk": "0.4.0-beta.58" }, "devDependencies": { "@types/node": "^22.16.2", From eb5dfd437726c23b7d3dbaa7f7ee3b1a030e8f84 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Fri, 6 Mar 2026 13:47:34 -0700 Subject: [PATCH 48/49] Fix packaging guide URL in CONTRIBUTING.md Co-Authored-By: Claude Opus 4.6 --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 663911b..cbc38a7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ ## Building and Development -See the [StartOS Packaging Guide](https://docs.start9.com/packaging-guide/) for complete environment setup and build instructions. +See the [StartOS Packaging Guide](https://docs.start9.com/packaging/) for complete environment setup and build instructions. ### Quick Start From 09bf470134cde3e3e638cfc8cd9da7ab33eb1bc6 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Tue, 10 Mar 2026 17:06:53 -0600 Subject: [PATCH 49/49] Update start-sdk to beta.59 Co-Authored-By: Claude Opus 4.6 --- package-lock.json | 8 ++++---- package.json | 2 +- startos/install/versions/index.ts | 2 +- .../{v1.5.0_0-beta.0.ts => v1.5.0.1.b0.ts} | 14 +++++++------- 4 files changed, 13 insertions(+), 13 deletions(-) rename startos/install/versions/{v1.5.0_0-beta.0.ts => v1.5.0.1.b0.ts} (77%) diff --git a/package-lock.json b/package-lock.json index c78b5c7..ab5f5e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "lnbits-startos", "dependencies": { - "@start9labs/start-sdk": "0.4.0-beta.58" + "@start9labs/start-sdk": "^0.4.0-beta.59" }, "devDependencies": { "@types/node": "^22.16.2", @@ -49,9 +49,9 @@ } }, "node_modules/@start9labs/start-sdk": { - "version": "0.4.0-beta.58", - "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.58.tgz", - "integrity": "sha512-d8R+g9WNpdxvvPyBDL2VGVJjmhleIV0JEOKm28JrhnfmNSf9gOLY40QzUV1PuSCNXl50dYiom/SXab1q0faidw==", + "version": "0.4.0-beta.59", + "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.59.tgz", + "integrity": "sha512-Cp8r+bg1feNAAHyWtND8kpbfrK3crL3WS6JCp1+LJdx8OvTTGXg/2znMOoAn+J9pBbAui5CtXiJigC1OaACYVw==", "license": "MIT", "dependencies": { "@iarna/toml": "^3.0.0", diff --git a/package.json b/package.json index 964470a..c7beb00 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "check": "tsc --noEmit" }, "dependencies": { - "@start9labs/start-sdk": "0.4.0-beta.58" + "@start9labs/start-sdk": "^0.4.0-beta.59" }, "devDependencies": { "@types/node": "^22.16.2", diff --git a/startos/install/versions/index.ts b/startos/install/versions/index.ts index 0bb157a..1e44859 100644 --- a/startos/install/versions/index.ts +++ b/startos/install/versions/index.ts @@ -1,3 +1,3 @@ -export { v1_5_0_0 as current } from './v1.5.0_0-beta.0' +export { v_1_5_0_1_b0 as current } from './v1.5.0.1.b0' export const other = [] diff --git a/startos/install/versions/v1.5.0_0-beta.0.ts b/startos/install/versions/v1.5.0.1.b0.ts similarity index 77% rename from startos/install/versions/v1.5.0_0-beta.0.ts rename to startos/install/versions/v1.5.0.1.b0.ts index d1c1b14..948c88b 100644 --- a/startos/install/versions/v1.5.0_0-beta.0.ts +++ b/startos/install/versions/v1.5.0.1.b0.ts @@ -3,14 +3,14 @@ import { rm } from 'fs/promises' import { envFile } from '../../fileModels/env' import { sdk } from '../../sdk' -export const v1_5_0_0 = VersionInfo.of({ - version: '1.5.0:0-beta.0', +export const v_1_5_0_1_b0 = VersionInfo.of({ + version: '1.5.0:1-beta.0', releaseNotes: { - en_US: 'Revamped for StartOS 0.4.0', - es_ES: 'Renovado para StartOS 0.4.0', - de_DE: 'Überarbeitet für StartOS 0.4.0', - pl_PL: 'Przeprojektowany dla StartOS 0.4.0', - fr_FR: 'Refait pour StartOS 0.4.0', + en_US: 'Update to StartOS SDK beta.59', + es_ES: 'Actualización a StartOS SDK beta.59', + de_DE: 'Update auf StartOS SDK beta.59', + pl_PL: 'Aktualizacja do StartOS SDK beta.59', + fr_FR: 'Mise à jour vers StartOS SDK beta.59', }, migrations: { up: async ({ effects }) => {