diff --git a/.github/workflows/buildService.yml b/.github/workflows/buildService.yml new file mode 100644 index 0000000..f013351 --- /dev/null +++ b/.github/workflows/buildService.yml @@ -0,0 +1,36 @@ +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@v3 + + - name: Build the service package + id: build + run: | + git submodule update --init --recursive + start-sdk init + make + PACKAGE_ID=$(yq -oy ".id" manifest.*) + echo "::set-output name=package_id::$PACKAGE_ID" + shell: bash + + - name: Upload .s9pk + uses: actions/upload-artifact@v3 + with: + name: ${{ steps.build.outputs.package_id }}.s9pk + path: ./${{ steps.build.outputs.package_id }}.s9pk \ No newline at end of file diff --git a/.github/workflows/releaseService.yml b/.github/workflows/releaseService.yml new file mode 100644 index 0000000..ee85271 --- /dev/null +++ b/.github/workflows/releaseService.yml @@ -0,0 +1,71 @@ +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@v3 + + - 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 "::set-output name=package_id::$(yq -oy ".id" manifest.*)" + echo "::set-output name=package_title::$(yq -oy ".title" manifest.*)" + shell: bash + + - name: Generate sha256 checksum + run: | + PACKAGE_ID=${{ steps.package.outputs.package_id }} + sha256sum ${PACKAGE_ID}.s9pk > ${PACKAGE_ID}.s9pk.sha256 + shell: bash + + - name: Generate changelog + run: | + PACKAGE_ID=${{ steps.package.outputs.package_id }} + echo "## What's Changed" > change-log.txt + yq e '.release-notes' manifest.yaml >> 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@v1 + with: + tag_name: ${{ github.ref_name }} + name: ${{ steps.package.outputs.package_title }} ${{ github.ref_name }} + prerelease: true + body_path: change-log.txt + files: | + ./${{ steps.package.outputs.package_id }}.s9pk + ./${{ steps.package.outputs.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 ${{ steps.package.outputs.package_id }}.s9pk + fi \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2603f70 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.s9pk +startos/*.js +node_modules/ +.DS_Store +.vscode/ +docker-images +javascript \ No newline at end of file diff --git a/LICENSE b/LICENSE index d4c628b..793257b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Tunnelsats +Copyright (c) 2022 Start9 Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..21ba290 --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ +PACKAGE_ID := tunnelsats + +# Phony targets +.PHONY: all clean install + +# Default target +all: ${PACKAGE_ID}.s9pk + +# Build targets +${PACKAGE_ID}.s9pk: $(shell start-cli s9pk list-ingredients) + start-cli s9pk pack + +javascript/index.js: $(shell git ls-files startos) tsconfig.json node_modules package.json + npm run build + +node_modules: package.json package-lock.json + npm ci + +package-lock.json: package.json + npm i + +# Clean target +clean: + rm -rf ${PACKAGE_ID}.s9pk + rm -rf javascript + rm -rf node_modules + +# Install target +install: + @if [ ! -f ~/.startos/config.yaml ]; then echo "You must define \"host: http://server-name.local\" in ~/.startos/config.yaml config file first."; exit 1; fi + @echo "\nInstalling to $$(grep -v '^#' ~/.startos/config.yaml | cut -d'/' -f3) ...\n" + @[ -f $(PACKAGE_ID).s9pk ] || ( $(MAKE) && echo "\nInstalling to $$(grep -v '^#' ~/.startos/config.yaml | cut -d'/' -f3) ...\n" ) + @start-cli package install -s $(PACKAGE_ID).s9pk diff --git a/README.md b/README.md index 8414f91..7c1fc75 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ -# startos -Providing fast, anonymous and reliable VPN for start9 OS runners +# Tunnel Sats for StartOS + +A VPN service for lightning implementations. + +## Building from source + +`npm i` +`make` + diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..4f56932 Binary files /dev/null and b/icon.png differ diff --git a/instructions.md b/instructions.md new file mode 100644 index 0000000..9540dd1 --- /dev/null +++ b/instructions.md @@ -0,0 +1,8 @@ +# Tunnel Sats for StartOS + +A VPN service for lightning implementations. + +## Building from source + +`npm i` +`make` diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..fa7fa37 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,208 @@ +{ + "name": "tunnelsats-startos", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@start9labs/start-sdk": "0.3.6-alpha.22" + }, + "devDependencies": { + "@types/node": "^22.1.0", + "@vercel/ncc": "^0.38.1", + "prettier": "^3.2.5", + "typescript": "^5.4.3" + } + }, + "node_modules/@iarna/toml": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==" + }, + "node_modules/@noble/curves": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@start9labs/start-sdk": { + "version": "0.3.6-alpha.22", + "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.3.6-alpha.22.tgz", + "integrity": "sha512-TmzHolOU6Uh/T5yBffh5j+h2gFKu/HEB6FBpxoYJ9je8+uXGfJ0rOwa0nFzKAcNhqpi7rgfn/gqor8+QCkZS4w==", + "license": "MIT", + "dependencies": { + "@iarna/toml": "^2.2.5", + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.4.0", + "isomorphic-fetch": "^3.0.0", + "lodash.merge": "^4.6.2", + "mime-types": "^2.1.35", + "ts-matches": "^6.2.1", + "yaml": "^2.2.2" + } + }, + "node_modules/@types/node": { + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.1.0.tgz", + "integrity": "sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==", + "dev": true, + "dependencies": { + "undici-types": "~6.13.0" + } + }, + "node_modules/@vercel/ncc": { + "version": "0.38.1", + "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.38.1.tgz", + "integrity": "sha512-IBBb+iI2NLu4VQn3Vwldyi2QwaXt5+hTyh58ggAMoCGE6DJmPvwL3KPBWcJl1m9LYPChBLE980Jw+CS4Wokqxw==", + "dev": true, + "bin": { + "ncc": "dist/ncc/cli.js" + } + }, + "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==", + "dependencies": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/prettier": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", + "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", + "dev": true, + "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==" + }, + "node_modules/ts-matches": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ts-matches/-/ts-matches-6.2.1.tgz", + "integrity": "sha512-qdnMgTHsGCEGGK6QiaNMY2vD9eQtRp2Q+pAxcOAzxHJKDKTBYsc1ISTg1zp8H2+EmtCB0eko/1TwYUA5/mUGug==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.13.0.tgz", + "integrity": "sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==", + "dev": true + }, + "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==" + }, + "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==" + }, + "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==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/yaml": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", + "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..dac157a --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "hello-world-startos", + "scripts": { + "build": "rm -rf ./javascript && ncc build startos/index.ts -o ./javascript", + "prettier": "prettier --write startos", + "check": "tsc --noEmit" + }, + "dependencies": { + "@start9labs/start-sdk": "0.3.6-alpha.22" + }, + "devDependencies": { + "@types/node": "^22.1.0", + "@vercel/ncc": "^0.38.1", + "prettier": "^3.2.5", + "typescript": "^5.4.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..1866c78 --- /dev/null +++ b/startos/actions/index.ts @@ -0,0 +1,9 @@ +import { sdk } from '../sdk' +import { showSecretPhrase } from './showSecretPhrase' +import { setName } from './setName' +import { nameToLogs } from './nameToLogs' + +export const actions = sdk.Actions.of() + .addAction(setName) + .addAction(showSecretPhrase) + .addAction(nameToLogs) diff --git a/startos/actions/nameToLogs.ts b/startos/actions/nameToLogs.ts new file mode 100644 index 0000000..bfe74a6 --- /dev/null +++ b/startos/actions/nameToLogs.ts @@ -0,0 +1,36 @@ +import { sdk } from '../sdk' +import { yamlFile } from '../file-models/config.yml' + +export const nameToLogs = sdk.Action.withoutInput( + // id + 'name-to-logs', + + // metadata + async ({ effects }) => ({ + name: 'Print name to Logs', + description: 'Prints "Hello [Name]" to the service logs.', + warning: null, + allowedStatuses: 'only-running', + group: null, + visibility: (await sdk.store + .getOwn(effects, sdk.StorePath.nameLastUpdatedAt) + .const()) + ? 'enabled' + : { + disabled: 'Cannot print name to logs until you update your name.', + }, + }), + + // the execution function + async ({ effects }) => { + const name = (await yamlFile.read.const(effects))!.name + console.info(`Hello ${name}`) + + return { + version: '1', + title: 'Success', + message: `"Hello ${name}" has been logged. Open the Hello World service logs to view it.`, + result: null, + } + }, +) diff --git a/startos/actions/setName.ts b/startos/actions/setName.ts new file mode 100644 index 0000000..5fb74fb --- /dev/null +++ b/startos/actions/setName.ts @@ -0,0 +1,57 @@ +import { sdk } from '../sdk' +import { yamlFile } from '../file-models/config.yml' +import { getSecretPhrase } from '../utils' + +const { InputSpec, Value } = sdk + +export const inputSpec = InputSpec.of({ + name: Value.text({ + name: 'Name', + description: + 'When you launch the Hello World UI, it will display "Hello [Name]"', + required: true, + default: 'World', + }), +}) + +export const setName = sdk.Action.withInput( + // id + 'set-name', + + // metadata + async ({ effects }) => ({ + name: 'Set Name', + description: 'Set your name so Hello World can say hello to you', + warning: null, + allowedStatuses: 'any', + group: null, + visibility: 'enabled', + }), + + // form input specification + inputSpec, + + // optionally pre-fill the input form + ({ effects }) => yamlFile.read.const(effects), + + // the execution function + async ({ effects, input }) => { + const yaml = await yamlFile.read.const(effects) + + if (yaml?.name === input.name) return + + await Promise.all([ + yamlFile.merge(input), + sdk.store.setOwn( + effects, + sdk.StorePath.secretPhrase, + getSecretPhrase(input.name), + ), + sdk.store.setOwn( + effects, + sdk.StorePath.nameLastUpdatedAt, + new Date().toISOString(), + ), + ]) + }, +) diff --git a/startos/actions/showSecretPhrase.ts b/startos/actions/showSecretPhrase.ts new file mode 100644 index 0000000..092eb66 --- /dev/null +++ b/startos/actions/showSecretPhrase.ts @@ -0,0 +1,33 @@ +import { sdk } from '../sdk' + +export const showSecretPhrase = sdk.Action.withoutInput( + // id + 'show-secret-phrase', + + // metadata + async ({ effects }) => ({ + name: 'Show Secret Phrase', + description: 'Reveal the secret phrase for Hello World', + warning: null, + allowedStatuses: 'any', + group: null, + visibility: 'enabled', + }), + + // the execution function + async ({ effects }) => ({ + version: '1', + title: 'Secret Phrase', + message: + 'Below is your secret phrase. Use it to gain access to extraordinary places', + result: { + type: 'single', + value: await sdk.store + .getOwn(effects, sdk.StorePath.secretPhrase) + .const(), + copyable: true, + qr: true, + masked: true, + }, + }), +) diff --git a/startos/backups.ts b/startos/backups.ts new file mode 100644 index 0000000..6ccffb6 --- /dev/null +++ b/startos/backups.ts @@ -0,0 +1,5 @@ +import { sdk } from './sdk' + +export const { createBackup, restoreBackup } = sdk.setupBackups( + async ({ effects }) => sdk.Backups.volumes('main'), +) diff --git a/startos/dependencies.ts b/startos/dependencies.ts new file mode 100644 index 0000000..84c5f3d --- /dev/null +++ b/startos/dependencies.ts @@ -0,0 +1,9 @@ +import { sdk } from './sdk' + +export const setDependencies = sdk.setupDependencies(async ({ effects }) => ({ + lnd: { + kind: 'running', + versionRange: '>=0.17.1 <0.19.0', + healthChecks: [], + }, +})) \ No newline at end of file diff --git a/startos/file-models/config.yml.ts b/startos/file-models/config.yml.ts new file mode 100644 index 0000000..44d8da4 --- /dev/null +++ b/startos/file-models/config.yml.ts @@ -0,0 +1,11 @@ +import { matches, FileHelper } from '@start9labs/start-sdk' +const { object, string } = matches + +const shape = object({ + name: string.optional().onMismatch(undefined), +}) + +export const yamlFile = FileHelper.yaml( + '/media/startos/volumes/main/config.yml', + shape, +) diff --git a/startos/file-models/lnd.conf.ts b/startos/file-models/lnd.conf.ts new file mode 100644 index 0000000..8c9623f --- /dev/null +++ b/startos/file-models/lnd.conf.ts @@ -0,0 +1,50 @@ +import { matches, FileHelper, utils } from '@start9labs/start-sdk' + + +// Todo: adjust for TS and lnd.conf + +const { object, string, literal } = matches + +const lndRpcserver = 'lnd.startos:10009' + +const shape = object({ + uipassword: string.onMismatch(utils.getDefaultString(randomPassword)), + 'lit-dir': literal(litDir).onMismatch(litDir), + 'insecure-httplisten': + literal(insecureHttplisten).onMismatch(insecureHttplisten), + 'remote.lnd.rpcserver': literal(lndRpcserver).onMismatch(lndRpcserver), + 'remote.lnd.macaroonpath': + literal(lndMacaroonpath).onMismatch(lndMacaroonpath), + 'remote.lnd.tlscertpath': literal(lndTlscertpath).onMismatch(lndTlscertpath), +}) + +function fromLitConf(text: string): typeof shape._TYPE { + let conf: Record = {} + const lines = text.split('\n') + + for (const line of lines) { + const [key, value] = line.split('=', 2) + const trimmedKey = key.trim() + const trimmedValue = value.trim() + + conf[trimmedKey] = trimmedValue + } + + return conf as typeof shape._TYPE +} + +function toLitConf(obj: typeof shape._TYPE): string { + let litConf = '' + for (const [key, value] of Object.entries(obj)) { + litConf += `${key}=${value}\n` + } + + return litConf +} + +export const litConfig = FileHelper.raw( + `${litDir}/lit.conf`, + (obj: typeof shape._TYPE) => toLitConf(obj), + (str) => fromLitConf(str), + (value) => shape.unsafeCast(value), +) \ No newline at end of file diff --git a/startos/index.ts b/startos/index.ts new file mode 100644 index 0000000..8d32c10 --- /dev/null +++ b/startos/index.ts @@ -0,0 +1,11 @@ +/** + * Plumbing. DO NOT EDIT. + */ +export { createBackup, restoreBackup } from './backups' +export { main } from './main' +export { packageInit, packageUninit, containerInit } from './init' +export { actions } from './actions' +import { buildManifest } from '@start9labs/start-sdk' +import { manifest as sdkManifest } from './manifest' +import { versions } from './versions' +export const manifest = buildManifest(versions, sdkManifest) diff --git a/startos/init.ts b/startos/init.ts new file mode 100644 index 0000000..5939cda --- /dev/null +++ b/startos/init.ts @@ -0,0 +1,37 @@ +import { sdk } from './sdk' +import { exposedStore } from './store' +import { setDependencies } from './dependencies' +import { setInterfaces } from './interfaces' +import { versions } from './versions' +import { actions } from './actions' +import { getSecretPhrase } from './utils' +import { yamlFile } from './file-models/config.yml' + +// **** Install **** +const install = sdk.setupInstall(async ({ effects }) => { + const name = 'World' + + await yamlFile.write({ name }) + + await sdk.store.setOwn( + effects, + sdk.StorePath.secretPhrase, + getSecretPhrase(name), + ) +}) + +// **** Uninstall **** +const uninstall = sdk.setupUninstall(async ({ effects }) => {}) + +/** + * Plumbing. DO NOT EDIT. + */ +export const { packageInit, packageUninit, containerInit } = sdk.setupInit( + versions, + install, + uninstall, + setInterfaces, + setDependencies, + actions, + exposedStore, +) diff --git a/startos/interfaces.ts b/startos/interfaces.ts new file mode 100644 index 0000000..4786c30 --- /dev/null +++ b/startos/interfaces.ts @@ -0,0 +1,25 @@ +import { sdk } from './sdk' +import { uiPort } from './utils' + +export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => { + const uiMulti = sdk.host.multi(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 Hello World', + type: 'ui', + hasPrimary: false, + masked: false, + schemeOverride: null, + username: null, + path: '', + search: {}, + }) + + const uiReceipt = await uiMultiOrigin.export([ui]) + + return [uiReceipt] +}) diff --git a/startos/main.ts b/startos/main.ts new file mode 100644 index 0000000..7411e69 --- /dev/null +++ b/startos/main.ts @@ -0,0 +1,42 @@ +import { sdk } from './sdk' +import { T } from '@start9labs/start-sdk' +import { 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 TunnelSats!') + + /** + * ======================== Additional Health Checks (optional) ======================== + * + * In this section, we define *additional* health checks beyond those included with each daemon (below). + */ + const healthReceipts: T.HealthReceipt[] = [] + + /** + * ======================== 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, healthReceipts).addDaemon('primary', { + image: { id: 'tunnelsats' }, // Must match an Image ID declared in the manifest. + command: ['tunnelsats'], // The command to start the daemon. + mounts: sdk.Mounts.of().addVolume('main', null, '/data', false), // Mount necessary volumes. ID must match manifest declaration. + ready: { + display: 'Web Interface', // If null, the health check will NOT be displayed to the user. If provided, this string will be the name of the health check and displayed to the user. + // A function below determines the health status of this daemon + fn: () => + sdk.healthCheck.checkPortListening(effects, uiPort, { + successMessage: 'The web interface is ready', + errorMessage: 'The web interface is not ready', + }), + }, + requires: ['lnd', 'wireguard'], // If this daemon depends on the successful initialization of one or more prior daemons, enter their IDs here. + }) +}) diff --git a/startos/manifest.ts b/startos/manifest.ts new file mode 100644 index 0000000..89bfc67 --- /dev/null +++ b/startos/manifest.ts @@ -0,0 +1,35 @@ +import { setupManifest } from '@start9labs/start-sdk' + +export const manifest = setupManifest({ + id: 'tunnelsats', + title: 'TunnelSats', + license: 'mit', + wrapperRepo: 'https://github.com/Tunnelsats/startos', + upstreamRepo: 'https://github.com/Tunnelsats/tunnelsats', + supportSite: 'https://guide.tunnelsats.com/FAQ.html', + marketingSite: 'https://tunnelsats.com/', + donationUrl: 'https://ln.tunnelsats.com/lnurlp/link/AJo7s4', + description: { + short: 'VPN Connection For Your Lightning Node', + long: 'Providing Lightning ⚡ Services is about privacy, reliability, connectivity, speed and liquidity. Relying your node connectivity to a single service Tor is a risk regarding connectivity and network stability, as anyone running a lightning node can testify. With Hybrid connectivity, you offer your payment and routing services to be faster, more reliable, and yet, there is a privacy concern when you do it with your home-IP: you both expose your rough location of your node, potentially your home and your node\'s system to attacks from the internet. With our solution Tunnel⚡Sats, you get the best of both worlds. Your node and home IP stays hidden, behind Tor and our VPS public IP address, which will be your node\'s face to the public internet, is shared with other peers. You may see higher reliability causing not only higher uptime, fewer offline peer nodes but also greater routing numbers. This isn\'t a promise, but an eventually expected outcome.', + }, + assets: [], + volumes: ['main'], + images: { + 'tunnelsats': { + source: { + dockerTag: 'start9/tunnelsats', + }, + }, + }, + hardwareRequirements: {}, + alerts: { + install: 'Optional alert to display before installing the service', + update: null, + uninstall: null, + restore: null, + start: null, + stop: null, + }, + dependencies: {}, +}) diff --git a/startos/sdk.ts b/startos/sdk.ts new file mode 100644 index 0000000..26c3015 --- /dev/null +++ b/startos/sdk.ts @@ -0,0 +1,13 @@ +import { StartSdk } from '@start9labs/start-sdk' +import { manifest } from './manifest' +import { Store } from './store' + +/** + * Plumbing. DO NOT EDIT. + * + * The exported "sdk" const is used throughout this package codebase. + */ +export const sdk = StartSdk.of() + .withManifest(manifest) + .withStore() + .build(true) diff --git a/startos/store.ts b/startos/store.ts new file mode 100644 index 0000000..7f7b4e4 --- /dev/null +++ b/startos/store.ts @@ -0,0 +1,30 @@ +import { setupExposeStore } from '@start9labs/start-sdk' + +/** + * @description The Store is used for persisting arbitrary data that are needed by the wrapper + * package but are NOT persisted by the upstream service. Do NOT persist data here that are + * already being persisted by the service itself. + * + * Store data should be kept to a minimum. Stateless packages are easier to maintain + * and eliminate unexpected behavior. + * @type {Record} + * @example + * ``` + * export type Store = { + * key1: string + * key2: boolean + * key3: number + * key4: { + * key5: string[] + * } + * } + * ``` + */ +export type Store = { + secretPhrase: string + nameLastUpdatedAt: string | null +} + +export const exposedStore = setupExposeStore((pathBuilder) => [ + pathBuilder.nameLastUpdatedAt, +]) diff --git a/startos/utils.ts b/startos/utils.ts new file mode 100644 index 0000000..c9c17fb --- /dev/null +++ b/startos/utils.ts @@ -0,0 +1,4 @@ +// Here we define any constants or functions that are shared by multiple components +// throughout the package codebase. This file will be unnecessary for many packages. + +// export const tsDir = '/data/ts' diff --git a/startos/versions/index.ts b/startos/versions/index.ts new file mode 100644 index 0000000..339e78f --- /dev/null +++ b/startos/versions/index.ts @@ -0,0 +1,4 @@ +import { VersionGraph } from '@start9labs/start-sdk' +import { v0360 } from './v0.3.6.0' + +export const versions = VersionGraph.of(v0360) diff --git a/startos/versions/v0.3.6.0.ts b/startos/versions/v0.3.6.0.ts new file mode 100644 index 0000000..ed8ffc7 --- /dev/null +++ b/startos/versions/v0.3.6.0.ts @@ -0,0 +1,14 @@ +import { VersionInfo, IMPOSSIBLE } from '@start9labs/start-sdk' +import { sdk } from '../sdk' +import { setName } from '../actions/setName' + +export const v0360 = VersionInfo.of({ + version: '0.3.6:0', + releaseNotes: 'Revamped for StartOS 0.3.6', + migrations: { + up: async ({ effects }) => { + await sdk.action.requestOwn(effects, setName, 'critical') + }, + down: IMPOSSIBLE, + }, +}) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..039d1ff --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "include": ["startos/**/*.ts", "node_modules/**/startos"], + "compilerOptions": { + "target": "ES2022", + "module": "None", + "moduleResolution": "node", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true + } +}