-
+## Building from source
+
+1. Set up your [environment](https://docs.start9.com/packaging-guide/environment-setup.html).
+
+1. Clone this repository and `cd` into it.
+
+1. run `make`.
+
+1. The resulting `.s9pk` can be side loaded into StartOS.
+
+For a complete list of build options, see the [docs](https://docs.start9.com/packaging-guide/building.html)
diff --git a/package-lock.json b/package-lock.json
index d55e047..0443869 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.45"
},
"devDependencies": {
"@types/node": "^22.18.6",
@@ -49,9 +49,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.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",
@@ -73,9 +73,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": {
@@ -165,9 +165,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": {
@@ -193,9 +193,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": {
@@ -236,15 +236,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 525cd21..c8a3414 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.45"
},
"devDependencies": {
"@types/node": "^22.18.6",
diff --git a/startos/main.ts b/startos/main.ts
index 523052c..79765e7 100644
--- a/startos/main.ts
+++ b/startos/main.ts
@@ -2,7 +2,7 @@ import { sdk } from './sdk'
import { T } from '@start9labs/start-sdk'
import { smpPort, xftpPort } from './utils'
-export const main = sdk.setupMain(async ({ effects, started }) => {
+export const main = sdk.setupMain(async ({ effects }) => {
/**
* ======================== Setup (optional) ========================
*
@@ -22,13 +22,6 @@ export const main = sdk.setupMain(async ({ effects, started }) => {
'simplex-sub',
)
- /**
- * ======================== Additional Health Checks (optional) ========================
- *
- * In this section, we define *additional* health checks beyond those included with each daemon (below).
- */
- const additionalChecks: T.HealthCheck[] = []
-
/**
* ======================== Daemons ========================
*
@@ -36,7 +29,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)
+ return sdk.Daemons.of(effects)
.addDaemon('smp', {
subcontainer: simplexSub,
exec: { command: ['smp-server', 'start', '+RTS', '-N', '-RTS'] },
diff --git a/startos/manifest.ts b/startos/manifest.ts
index 0176cc0..4e7ef1a 100644
--- a/startos/manifest.ts
+++ b/startos/manifest.ts
@@ -1,12 +1,6 @@
import { setupManifest } from '@start9labs/start-sdk'
-import { SDKImageInputSpec } from '@start9labs/start-sdk/base/lib/types/ManifestTypes'
import { source_code } from './utils'
-const BUILD = process.env.BUILD || ''
-
-const architectures =
- BUILD === 'x86_64' || BUILD === 'aarch64' ? [BUILD] : ['x86_64', 'aarch64']
-
export const manifest = setupManifest({
id: 'simplex',
title: 'SimpleX Server',
@@ -30,19 +24,7 @@ export const manifest = setupManifest({
source: {
dockerBuild: {},
},
- arch: architectures,
- } as SDKImageInputSpec,
- },
- hardwareRequirements: {
- arch: architectures,
- },
- alerts: {
- install: null,
- update: null,
- uninstall: null,
- restore: null,
- start: null,
- stop: null,
+ },
},
dependencies: {},
})
From b870fc10ce85cdb9caf36dae1d72b4469778ae98 Mon Sep 17 00:00:00 2001
From: Matt Hill
Date: Sun, 21 Dec 2025 22:21:24 -0700
Subject: [PATCH 13/35] workflows
---
.github/workflows/buildService.yml | 16 ++++++++++++++++
.github/workflows/releaseService.yml | 16 ++++++++++++++++
2 files changed, 32 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..5d19b77
--- /dev/null
+++ b/.github/workflows/buildService.yml
@@ -0,0 +1,16 @@
+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:
+ 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
new file mode 100644
index 0000000..4764532
--- /dev/null
+++ b/.github/workflows/releaseService.yml
@@ -0,0 +1,16 @@
+name: Release Service
+
+on:
+ push:
+ tags:
+ - 'v*.*'
+
+jobs:
+ 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
From 34891bcc25e480eb4cb4e107a61b2dec580766ed Mon Sep 17 00:00:00 2001
From: Matt Hill
Date: Fri, 26 Dec 2025 11:32:51 -0700
Subject: [PATCH 14/35] beta ready
---
.gitmodules | 3 -
Dockerfile | 62 ------
old/.github/Dockerfile.simplex | 61 ------
.../workflows/build-simplex-server.yml | 78 --------
old/.github/workflows/buildService.yml | 37 ----
old/.github/workflows/releaseService.yml | 72 -------
old/assets/compat/backup.sh | 9 -
old/assets/compat/restore.sh | 7 -
old/check-syn-ack.sh | 17 --
old/docker_entrypoint.sh | 124 ------------
old/manifest.yaml | 131 ------------
old/scripts/bundle.ts | 6 -
old/scripts/deps.ts | 2 -
old/scripts/embassy.ts | 5 -
old/scripts/procedures/getConfig.ts | 24 ---
old/scripts/procedures/main.ts | 7 -
old/scripts/procedures/migrations.ts | 28 ---
old/scripts/procedures/properties.ts | 3 -
old/scripts/procedures/setConfig.ts | 9 -
package-lock.json | 39 +++-
package.json | 3 +-
simplexmq | 1 -
startos/backups.ts | 9 +-
startos/fileModels/fileServer.ini.ts | 84 ++++++++
startos/fileModels/ini-lib.ts | 157 +++++++++++++++
startos/fileModels/smp.ini.ts | 146 --------------
startos/fileModels/smpServer.ini.ts | 146 ++++++++++++++
startos/fileModels/xftp.ini.ts | 13 --
startos/init/index.ts | 2 +
startos/init/initServers.ts | 64 ++++++
startos/install/versionGraph.ts | 19 +-
startos/install/versions/index.ts | 2 +-
startos/install/versions/v6.3.2.1.ts | 15 --
startos/install/versions/v6.4.5.0.a0.ts | 121 +++++++++++
startos/interfaces.ts | 92 ++++++---
startos/main.ts | 49 +++--
startos/manifest.ts | 35 +++-
startos/utils.ts | 189 ++++++++++--------
38 files changed, 838 insertions(+), 1033 deletions(-)
delete mode 100644 .gitmodules
delete mode 100644 Dockerfile
delete mode 100644 old/.github/Dockerfile.simplex
delete mode 100644 old/.github/workflows/build-simplex-server.yml
delete mode 100644 old/.github/workflows/buildService.yml
delete mode 100644 old/.github/workflows/releaseService.yml
delete mode 100755 old/assets/compat/backup.sh
delete mode 100755 old/assets/compat/restore.sh
delete mode 100755 old/check-syn-ack.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.ts
delete mode 100644 old/scripts/procedures/getConfig.ts
delete mode 100644 old/scripts/procedures/main.ts
delete mode 100644 old/scripts/procedures/migrations.ts
delete mode 100644 old/scripts/procedures/properties.ts
delete mode 100644 old/scripts/procedures/setConfig.ts
delete mode 160000 simplexmq
create mode 100644 startos/fileModels/fileServer.ini.ts
create mode 100644 startos/fileModels/ini-lib.ts
delete mode 100644 startos/fileModels/smp.ini.ts
create mode 100644 startos/fileModels/smpServer.ini.ts
delete mode 100644 startos/fileModels/xftp.ini.ts
create mode 100644 startos/init/initServers.ts
delete mode 100644 startos/install/versions/v6.3.2.1.ts
create mode 100644 startos/install/versions/v6.4.5.0.a0.ts
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index 0b0940c..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "simplexmq"]
- path = simplexmq
- url = https://github.com/simplex-chat/simplexmq.git
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index bf71a4e..0000000
--- a/Dockerfile
+++ /dev/null
@@ -1,62 +0,0 @@
-ARG TAG=22.04
-
-FROM ubuntu:${TAG} AS build
-
-### Build stage
-
-# Install curl and git and simplexmq dependencies
-RUN apt-get update && apt-get install -y curl git build-essential libgmp3-dev zlib1g-dev llvm-12 llvm-12-dev libnuma-dev libssl-dev
-
-# Specify bootstrap Haskell versions
-ENV BOOTSTRAP_HASKELL_GHC_VERSION=9.6.3
-ENV BOOTSTRAP_HASKELL_CABAL_VERSION=3.10.1.0
-
-# Install ghcup
-RUN curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | BOOTSTRAP_HASKELL_NONINTERACTIVE=1 sh
-
-# Adjust PATH
-ENV PATH="/root/.cabal/bin:/root/.ghcup/bin:$PATH"
-
-# Set both as default
-RUN ghcup set ghc "${BOOTSTRAP_HASKELL_GHC_VERSION}" && \
- ghcup set cabal "${BOOTSTRAP_HASKELL_CABAL_VERSION}"
-
-COPY ./simplexmq /project
-WORKDIR /project
-
-# Compile apps
-RUN cabal update
-RUN cabal build exe:smp-server
-RUN cabal build exe:xftp-server
-
-# Create new path containing all files needed
-RUN mkdir /final
-WORKDIR /final
-
-# Strip the binaries from debug symbols to reduce size
-RUN for app in smp-server xftp-server; do \
- bin=$(find /project/dist-newstyle -name "$app" -type f -executable) && \
- mv "$bin" ./ && \
- strip ./"$app" &&\
- mv /project/scripts/docker/entrypoint-"$app" ./entrypoint; \
-done
-
-### Final stage
-FROM ubuntu:${TAG}
-
-# Install OpenSSL dependency
-RUN apt-get update \
- && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends openssl libnuma-dev tini netcat \
- && rm -rf /var/lib/apt/lists/*
-
-# Copy compiled apps from build stage
-COPY --from=build /final /usr/local/bin/
-
-# simplexmq requires using SIGINT to correctly preserve undelivered messages and restore them on restart
-STOPSIGNAL SIGINT
-
-# ADD ./check-syn-ack.sh /usr/local/bin/check-syn-ack.sh
-# RUN chmod a+x /usr/local/bin/check-syn-ack.sh
-
-# ADD ./docker_entrypoint.sh /usr/local/bin/docker_entrypoint.sh
-# RUN chmod a+x /usr/local/bin/docker_entrypoint.sh
diff --git a/old/.github/Dockerfile.simplex b/old/.github/Dockerfile.simplex
deleted file mode 100644
index db08ac5..0000000
--- a/old/.github/Dockerfile.simplex
+++ /dev/null
@@ -1,61 +0,0 @@
-ARG TAG=22.04
-
-FROM ubuntu:${TAG} AS build
-
-### Build stage
-
-# Install curl and git and simplexmq dependencies
-RUN apt-get update && apt-get install -y curl git build-essential libgmp3-dev zlib1g-dev llvm-12 llvm-12-dev libnuma-dev libssl-dev
-
-# Specify bootstrap Haskell versions
-ENV BOOTSTRAP_HASKELL_GHC_VERSION=9.6.3
-ENV BOOTSTRAP_HASKELL_CABAL_VERSION=3.10.1.0
-
-# Install ghcup
-RUN curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | BOOTSTRAP_HASKELL_NONINTERACTIVE=1 sh
-
-# Adjust PATH
-ENV PATH="/root/.cabal/bin:/root/.ghcup/bin:$PATH"
-
-# Set both as default
-RUN ghcup set ghc "${BOOTSTRAP_HASKELL_GHC_VERSION}" && \
- ghcup set cabal "${BOOTSTRAP_HASKELL_CABAL_VERSION}"
-
-COPY ./simplex-chat /project
-WORKDIR /project
-
-# Compile apps
-RUN cabal update
-RUN cabal build exe:smp-server
-RUN cabal build exe:xftp-server
-
-# Create new path containing all files needed
-RUN mkdir /final
-WORKDIR /final
-
-# Strip the binaries from debug symbols to reduce size
-RUN for app in smp-server xftp-server; do \
- bin=$(find / -name "$app" -type f -executable 2>/dev/null) && \
- if [ -n "$bin" ]; then \
- mv "$bin" ./ && \
- strip ./"$app" && \
- mv /project/scripts/docker/entrypoint-"$app" ./entrypoint; \
- else \
- echo "Binary $app not found"; \
- exit 1; \
- fi; \
-done
-
-### Final stage
-FROM ubuntu:${TAG}
-
-# Install OpenSSL dependency
-RUN apt-get update \
- && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends openssl libnuma-dev netcat \
- && rm -rf /var/lib/apt/lists/*
-
-# Copy compiled apps from build stage
-COPY --from=build /final /usr/local/bin/
-
-# simplexmq requires using SIGINT to correctly preserve undelivered messages and restore them on restart
-STOPSIGNAL SIGINT
diff --git a/old/.github/workflows/build-simplex-server.yml b/old/.github/workflows/build-simplex-server.yml
deleted file mode 100644
index f2a2241..0000000
--- a/old/.github/workflows/build-simplex-server.yml
+++ /dev/null
@@ -1,78 +0,0 @@
-name: Build and Publish SimpleX Docker Image
-
-on:
- workflow_dispatch:
- inputs:
- simplex_version:
- description: 'SimpleX Server version to build (e.g. v6.0.4 or v6.1.0-beta.1). Use "latest" to build the most recent version'
- required: true
- default: 'latest'
-
-jobs:
- build:
- runs-on: ubuntu-latest
-
- permissions:
- contents: read
- packages: write
- id-token: write
-
- env:
- REPO_URL: https://github.com/simplex-chat/simplex-chat.git
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
-
- - name: Set up QEMU
- uses: docker/setup-qemu-action@v3
- with:
- platforms: all
-
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
- with:
- install: true
-
- - name: Log in to GitHub Container Registry
- uses: docker/login-action@v3
- with:
- registry: ghcr.io
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Fetch latest tags from SimpleX repository
- run: |
- git clone --depth=1 --tags $REPO_URL
- cd simplex-chat
- # Find the latest tag, filtering out any non-version tags like 'v*'
- LATEST_TAG=$(git tag -l --sort=-v:refname 'v*' | head -n 1)
- echo "Latest tag found: $LATEST_TAG"
- echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_ENV
-
- - name: Determine version to build
- run: |
- if [ "${{ github.event.inputs.simplex_version }}" == "latest" ]; then
- echo "Using latest tag: $LATEST_TAG"
- echo "SIMPLEX_VERSION=$LATEST_TAG" >> $GITHUB_ENV
- else
- echo "Using specified version: ${{ github.event.inputs.simplex_version }}"
- echo "SIMPLEX_VERSION=${{ github.event.inputs.simplex_version }}" >> $GITHUB_ENV
- fi
-
- - name: Build and push Docker image for x86 and arm
- run: |
- echo "Building SimpleX version: $SIMPLEX_VERSION"
-
- REPO_NAME=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')
-
- docker buildx build \
- --platform linux/amd64,linux/arm64 \
- --file .github/Dockerfile.simplex \
- --tag ghcr.io/${REPO_NAME}:$SIMPLEX_VERSION \
- --build-arg SIMPLEX_VERSION=$SIMPLEX_VERSION \
- --push \
- .
-
- - name: Image build complete
- run: echo "Docker image has been built and published."
diff --git a/old/.github/workflows/buildService.yml b/old/.github/workflows/buildService.yml
deleted file mode 100644
index ab6e729..0000000
--- a/old/.github/workflows/buildService.yml
+++ /dev/null
@@ -1,37 +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
- 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/old/.github/workflows/releaseService.yml b/old/.github/workflows/releaseService.yml
deleted file mode 100644
index 6cf91f2..0000000
--- a/old/.github/workflows/releaseService.yml
+++ /dev/null
@@ -1,72 +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 }}
- printf "\n SHA256SUM: $(sha256sum ${PACKAGE_ID}.s9pk) \n"
- 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
diff --git a/old/assets/compat/backup.sh b/old/assets/compat/backup.sh
deleted file mode 100755
index 8b3436f..0000000
--- a/old/assets/compat/backup.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/sh
-
-set -e
-
-mkdir -p /mnt/backup/etc
-mkdir -p /mnt/backup/log
-compat duplicity create /mnt/backup/etc /etc/opt/simplex
-compat duplicity create /mnt/backup/etc /etc/opt/simplex-xftp
-compat duplicity create /mnt/backup/log /var/opt/simplex
\ No newline at end of file
diff --git a/old/assets/compat/restore.sh b/old/assets/compat/restore.sh
deleted file mode 100755
index e9fdcef..0000000
--- a/old/assets/compat/restore.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/sh
-
-set -e
-
-compat duplicity restore /mnt/backup/etc /etc/opt/simplex
-compat duplicity restore /mnt/backup/etc /etc/opt/simplex-xftp
-compat duplicity restore /mnt/backup/log /var/opt/simplex
\ No newline at end of file
diff --git a/old/check-syn-ack.sh b/old/check-syn-ack.sh
deleted file mode 100755
index 90f28b5..0000000
--- a/old/check-syn-ack.sh
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-
-read DURATION
-if [ "$DURATION" -le 10000 ]; then
- exit 60
-else
- output=$(nc -zv 127.0.0.1 5223 2>&1)
-
- expected_output="Connection to 127.0.0.1 5223 port [tcp/*] succeeded!"
-
- if [ "$output" == "$expected_output" ]; then
- exit 0
- else
- echo "The smp-server process does not seem to be responding to connection requests" >&2
- exit 1
- fi
-fi
\ No newline at end of file
diff --git a/old/docker_entrypoint.sh b/old/docker_entrypoint.sh
deleted file mode 100755
index 2b2e367..0000000
--- a/old/docker_entrypoint.sh
+++ /dev/null
@@ -1,124 +0,0 @@
-#!/usr/bin/env sh
-
-set -e
-printf "\n\n [i] Starting SimpleX ...\n\n"
-confd="/etc/opt/simplex"
-xftp="/etc/opt/simplex-xftp"
-logd="/var/opt/simplex"
-# Check if smp-server has been initialized
-if [ ! -f "$confd/smp-server.ini" ]; then
- # Set a 15 digit server password. See the comments in smp-server.ini for a description of what this does
- export PASS=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 15)
- # Init certificates and configs
- smp-server init -y -l --password $PASS
-else
- export PASS=$(grep -i "^create_password" $confd/smp-server.ini | awk -F ':' '{print $2}' | awk '{$1=$1};1')
-fi
-
-# Function to add or update a section in the smp-server.ini config file
-add_or_update_section() {
- local file="$1"
- local section="$2"
- local content="$3"
-
- if grep -q "^\[$section\]" "$file"; then
- # Section exists, update it
- awk -v section="$section" -v content="$content" '
- BEGIN { in_section = 0; printed = 0 }
- /^\[/ { if (in_section && !printed) { print content; printed = 1 }; in_section = 0 }
- /^\['"$section"'\]/ { in_section = 1; print; print content; printed = 1; next }
- { if (!in_section || !printed) print }
- END { if (in_section && !printed) print content }
- ' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
- echo "$section section updated in $file"
- else
- # Section doesn't exist, add it
- echo "\n[$section]\n$content" >> "$file"
- echo "$section section added to $file"
- fi
-}
-# Add or update INFORMATION section
-add_or_update_section "$confd/smp-server.ini" "INFORMATION" "source_code: https://github.com/simplex-chat/simplexmq"
-
-# Add or update PROXY section
-add_or_update_section "$confd/smp-server.ini" "PROXY" "socks_proxy: 172.18.0.1:9050\nsocks_mode: onion"
-
-# Add or update WEB section
-add_or_update_section "$confd/smp-server.ini" "WEB" "static_path: /var/opt/simplex/www"
-
-# Add or update TRANSPORT section with correct port
-add_or_update_section "$confd/smp-server.ini" "TRANSPORT" "host: \nport: 5223\nlog_tls_errors: off\nwebsockets: off"
-
-# Check if xftp-server has been initialized
-if [ ! -f "$xftp/file-server.ini" ]; then
- # Init certificates and configs
- mkdir -p /root/xftp
- xftp-server init -q '10gb' -p /root/xftp/
-
- else
- echo "All good! - XFTP initialized"
-fi
-
-# Backup store log just in case
-#
-# Uses the UTC (universal) time zone and this
-# format: YYYY-mm-dd'T'HH:MM:SS
-# year, month, day, letter T, hour, minute, second
-#
-# This is the ISO 8601 format without the time zone at the end.
-#
-_file="${logd}/smp-server-store.log"
-if [ -f "${_file}" ]; then
- _backup_extension="$(date -u '+%Y-%m-%dT%H:%M:%S')"
- cp -v -p "${_file}" "${_file}.${_backup_extension:-date-failed}"
- unset -v _backup_extension
-fi
-unset -v _file
-
-TOR_ADDRESS=$(sed -n -e 's/^tor-address: \(.*\)/\1/p' /root/start9/config.yaml)
-XFTP_ADDRESS=$(sed -n -e 's/^xftp-address: \(.*\)/\1/p' /root/start9/config.yaml)
-SMP_FINGERPRINT=$(cat $confd/fingerprint)
-XFTP_FINGERPRINT=$(cat $xftp/fingerprint)
-
-SMP_URL="smp://$SMP_FINGERPRINT:$PASS@$TOR_ADDRESS"
-XFTP_URL="xftp://$XFTP_FINGERPRINT@$XFTP_ADDRESS"
-mkdir -p /root/start9
-
-cat << EOF > /root/start9/stats.yaml
----
-version: 2
-data:
- SimpleX SMP Server Address:
- type: string
- value: $SMP_URL
- description: Your SMP Server address, used in client applications.
- copyable: true
- qr: true
- masked: true
- SimpleX XFTP Server Address:
- type: string
- value: $XFTP_URL
- description: Your XFTP Server address, used in client applications.
- copyable: true
- qr: true
- masked: true
-EOF
-
-# Run smp-server and xftp-server as background processes
-# Set up a trap to catch INT signal for graceful shutdown
-
-_term() {
- echo "Caught INT signal!"
- kill -INT "$smp_process" 2>/dev/null
- kill -INT "$xftp_process" 2>/dev/null
-}
-
-smp-server start +RTS -N -RTS &
-smp_process=$!
-
-xftp-server start +RTS -N -RTS &
-xftp_process=$!
-
-trap _term INT
-
-wait $xftp_process $smp_process
diff --git a/old/manifest.yaml b/old/manifest.yaml
deleted file mode 100644
index d87ae9d..0000000
--- a/old/manifest.yaml
+++ /dev/null
@@ -1,131 +0,0 @@
-id: simplex
-title: "SimpleX Server"
-version: 6.2.0.7
-release-notes: |
- - Updated to the latest upstream code with notable changes:
- * journal storage for messages (BETA).
- * prevent race condition when deleting queue and to avoid "orphan" messages.
- * Full change log available [here](https://github.com/simplex-chat/simplexmq/blob/master/CHANGELOG.md#620)
-license: MIT
-wrapper-repo: "https://github.com/Start9Labs/simplex-startos"
-upstream-repo: "https://github.com/simplex-chat/simplexmq/"
-support-site: "https://github.com/simplex-chat/simplexmq/issues"
-marketing-site: "https://simplex.chat/"
-donation-url: "https://github.com/simplex-chat/simplex-chat#help-us-with-donations"
-build: ["make"]
-description:
- short: An instant messenger without user IDs
- long: |
- SimpleX is a chat client that radically improves your privacy. It puts you in control of how people connect with you.
-assets:
- license: LICENSE
- icon: icon.png
- instructions: instructions.md
-main:
- type: docker
- image: main
- entrypoint: "docker_entrypoint.sh"
- args: []
- mounts:
- main: /root/
- conf: /etc/opt/simplex
- xftp: /etc/opt/simplex-xftp
- log: /var/opt/simplex
-hardware-requirements:
- arch:
- - x86_64
- - aarch64
-health-checks:
- api:
- name: Listening
- success-message: SimpleX Server is alive and accepting incoming connections
- type: docker
- image: main
- system: false
- entrypoint: check-syn-ack.sh
- args: []
- mounts: {}
- io-format: json
- inject: true
-config:
- get:
- type: script
- set:
- type: script
-properties:
- type: script
-volumes:
- main:
- type: data
- compat:
- type: assets
- conf:
- type: data
- xftp:
- type: data
- log:
- type: data
-interfaces:
- main:
- name: SimpleX SMP Interface
- description: Do not use the URL below. Instead, look in the Properties section to get your SimpleX server address.
- tor-config:
- port-mapping:
- 5223: "5223"
- ui: false
- protocols:
- - tcp
- xftp:
- name: SimpleX XFTP Interface
- description: Do not use the URL below. Instead, look in the Properties section to get your SimpleX server address.
- tor-config:
- port-mapping:
- 443: "443"
- ui: false
- protocols:
- - tcp
-dependencies: {}
-backup:
- create:
- type: docker
- image: compat
- system: true
- entrypoint: /assets/backup.sh
- args:
- - duplicity
- - create
- - /mnt/backup
- - /root/start9
- mounts:
- BACKUP: /mnt/backup
- compat: /assets
- main: /root/start9
- conf: /etc/opt/simplex
- xftp: /etc/opt/simplex-xftp
- log: /var/opt/simplex
- restore:
- type: docker
- image: compat
- system: true
- entrypoint: /assets/restore.sh
- args:
- - duplicity
- - restore
- - /mnt/backup
- - /root/start9
- mounts:
- BACKUP: /mnt/backup
- compat: /assets
- main: /root/start9
- conf: /etc/opt/simplex
- xftp: /etc/opt/simplex-xftp
- log: /var/opt/simplex
-migrations:
- from:
- "*":
- type: script
- args: ["from"]
- to:
- "*":
- type: script
- args: ["to"]
diff --git a/old/scripts/bundle.ts b/old/scripts/bundle.ts
deleted file mode 100644
index 07cbf3a..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);
diff --git a/old/scripts/deps.ts b/old/scripts/deps.ts
deleted file mode 100644
index 6519c00..0000000
--- a/old/scripts/deps.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from "https://deno.land/x/embassyd_sdk@v0.3.3.0.5/mod.ts";
-export * from "https://deno.land/x/embassyd_sdk@v0.3.3.0.5/util.ts";
\ No newline at end of file
diff --git a/old/scripts/embassy.ts b/old/scripts/embassy.ts
deleted file mode 100644
index 74f56eb..0000000
--- a/old/scripts/embassy.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export { setConfig } from "./procedures/setConfig.ts";
-export { getConfig } from "./procedures/getConfig.ts";
-export { properties } from "./procedures/properties.ts";
-export { migration } from "./procedures/migrations.ts";
-export { main } from "./procedures/main.ts";
\ No newline at end of file
diff --git a/old/scripts/procedures/getConfig.ts b/old/scripts/procedures/getConfig.ts
deleted file mode 100644
index 2e4503c..0000000
--- a/old/scripts/procedures/getConfig.ts
+++ /dev/null
@@ -1,24 +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 Embassy UI.
-
-import { compat, types as T } from "../deps.ts";
-
-export const getConfig: T.ExpectedExports.getConfig = compat.getConfig({
- "tor-address": {
- "name": "SMP Tor Address",
- "description": "The Tor address for SMP Server",
- "type": "pointer",
- "subtype": "package",
- "package-id": "simplex",
- "target": "tor-address",
- "interface": "main"
- },
- "xftp-address": {
- "name": "XFTP Tor Address",
- "description": "The Tor address for XFTP Server",
- "type": "pointer",
- "subtype": "package",
- "package-id": "simplex",
- "target": "tor-address",
- "interface": "xftp"
- }
- });
diff --git a/old/scripts/procedures/main.ts b/old/scripts/procedures/main.ts
deleted file mode 100644
index a426122..0000000
--- a/old/scripts/procedures/main.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { types as T, util } from "../deps.ts";
-
-export const main = async (effects: T.Effects) => {
- // args defaulted to [] - not necessary to include if empty
- await effects.runDaemon({ command: "docker_entrypoint.sh", args: [] }).wait();
- return util.ok;
-}
\ No newline at end of file
diff --git a/old/scripts/procedures/migrations.ts b/old/scripts/procedures/migrations.ts
deleted file mode 100644
index 55a55cf..0000000
--- a/old/scripts/procedures/migrations.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { compat, types as T } from "../deps.ts";
-
-export const migration: T.ExpectedExports.migration =
- compat.migrations.fromMapping(
- {
- "5.8.2": {
- up: compat.migrations.updateConfig(
- (config: any) => {
- config["xftp-address"];
-
- return config;
- },
- true,
- { version: "5.8.2", type: "up" }
- ),
- down: compat.migrations.updateConfig(
- (config: any) => {
- delete config["xftp-address"];
-
- return config;
- },
- true,
- { version: "5.8.2", type: "down" }
- ),
- },
- },
- "6.2.0.7"
- );
diff --git a/old/scripts/procedures/properties.ts b/old/scripts/procedures/properties.ts
deleted file mode 100644
index dff99aa..0000000
--- a/old/scripts/procedures/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/procedures/setConfig.ts b/old/scripts/procedures/setConfig.ts
deleted file mode 100644
index 2cea41b..0000000
--- a/old/scripts/procedures/setConfig.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { compat, types as T } from "../deps.ts";
-
-// deno-lint-ignore require-await
-export const setConfig: T.ExpectedExports.setConfig = async (
- effects: T.Effects,
- newConfig: T.Config,
-) => {
- return compat.setConfig(effects, newConfig);
-};
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 0443869..4e9ba96 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6,7 +6,8 @@
"": {
"name": "hello-world-startos",
"dependencies": {
- "@start9labs/start-sdk": "^0.4.0-beta.45"
+ "@start9labs/start-sdk": "^0.4.0-beta.45",
+ "tldts": "^7.0.19"
},
"devDependencies": {
"@types/node": "^22.18.6",
@@ -66,6 +67,15 @@
"yaml": "^2.7.1"
}
},
+ "node_modules/@start9labs/start-sdk/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/@types/ini": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@types/ini/-/ini-4.1.1.tgz",
@@ -101,15 +111,6 @@
"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",
@@ -180,6 +181,24 @@
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
+ "node_modules/tldts": {
+ "version": "7.0.19",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.19.tgz",
+ "integrity": "sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==",
+ "license": "MIT",
+ "dependencies": {
+ "tldts-core": "^7.0.19"
+ },
+ "bin": {
+ "tldts": "bin/cli.js"
+ }
+ },
+ "node_modules/tldts-core": {
+ "version": "7.0.19",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.19.tgz",
+ "integrity": "sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==",
+ "license": "MIT"
+ },
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
diff --git a/package.json b/package.json
index c8a3414..bb81a25 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,8 @@
"check": "tsc --noEmit"
},
"dependencies": {
- "@start9labs/start-sdk": "^0.4.0-beta.45"
+ "@start9labs/start-sdk": "^0.4.0-beta.45",
+ "tldts": "^7.0.19"
},
"devDependencies": {
"@types/node": "^22.18.6",
diff --git a/simplexmq b/simplexmq
deleted file mode 160000
index 79e9447..0000000
--- a/simplexmq
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 79e9447b73cc315ce35042b0a5f210c07ea39b07
diff --git a/startos/backups.ts b/startos/backups.ts
index 0a90b1e..729c896 100644
--- a/startos/backups.ts
+++ b/startos/backups.ts
@@ -1,5 +1,12 @@
import { sdk } from './sdk'
export const { createBackup, restoreInit } = sdk.setupBackups(
- async ({ effects }) => sdk.Backups.ofVolumes('main'),
+ async ({ effects }) =>
+ sdk.Backups.ofVolumes(
+ 'smp-configs',
+ 'smp-state',
+ 'xftp-configs',
+ 'xftp-state',
+ 'xftp-files',
+ ),
)
diff --git a/startos/fileModels/fileServer.ini.ts b/startos/fileModels/fileServer.ini.ts
new file mode 100644
index 0000000..c940d3d
--- /dev/null
+++ b/startos/fileModels/fileServer.ini.ts
@@ -0,0 +1,84 @@
+import { matches, FileHelper } from '@start9labs/start-sdk'
+import { xftpConfigDefaults } from '../utils'
+
+const { object, literals, natural, string, literal } = matches
+
+const { STORE_LOG, AUTH, TRANSPORT, FILES, INACTIVE_CLIENTS } =
+ xftpConfigDefaults
+
+const shape = object({
+ STORE_LOG: object({
+ /**
+ The server uses STM memory for persistence,
+ that will be lost on restart (e.g., as with redis).
+ This option enables saving memory to append only log,
+ and restoring it when the server is started.
+ Log is compacted on start (deleted objects are removed).
+ */
+ enable: literals('on', 'off').onMismatch(STORE_LOG.enable),
+ /**
+ Expire files after the specified number of hours.
+ */
+ expire_files_hours: natural
+ .optional()
+ .onMismatch(STORE_LOG.expire_files_hours),
+ /**
+ Enable xftp-server statistics for Grafana dashboard
+ */
+ log_stats: literals('on', 'off').onMismatch(STORE_LOG.log_stats),
+ /**
+ Log interval for real-time Prometheus metrics, in seconds
+ */
+ prometheus_interval: natural
+ .optional()
+ .onMismatch(STORE_LOG.prometheus_interval),
+ }),
+
+ AUTH: object({
+ /**
+ Set new_files option to off to completely prohibit uploading new files.
+ This can be useful when you want to decommission the server, but still allow downloading the existing files.
+ */
+ new_files: literals('on', 'off').onMismatch(AUTH.new_files),
+ /**
+ Use create_password option to enable basic auth to upload new files.
+ The password should be used as part of server address in client configuration:
+ xftp://fingerprint:password@host1,host2
+ The password will not be shared with file recipients, you must share it only
+ with the users who you want to allow uploading files to your server.
+ Password to upload files (any printable ASCII characters without whitespace, '@', ':' and '/')
+ */
+ create_password: string,
+ control_port_admin_password: string.optional().onMismatch(AUTH.new_files),
+ control_port_user_password: string.optional().onMismatch(AUTH.new_files),
+ }),
+ TRANSPORT: object({
+ /**
+ host is only used to print server address on start
+ */
+ host: string,
+ port: literal(TRANSPORT.port).onMismatch(TRANSPORT.port),
+ log_tls_errors: literals('on', 'off').onMismatch(TRANSPORT.log_tls_errors),
+ control_port: natural.optional().onMismatch(TRANSPORT.control_port),
+ }),
+ FILES: object({
+ path: literal(FILES.path),
+ storage_quota: string.onMismatch(FILES.storage_quota),
+ }),
+ INACTIVE_CLIENTS: object({
+ /**
+ Whether or not to disconnect inactive clients. If set, ttl and check_interval should also be set (seconds)
+ */
+ disconnect: literals(INACTIVE_CLIENTS.disconnect).onMismatch(
+ INACTIVE_CLIENTS.disconnect,
+ ),
+ }),
+})
+
+export const fileServerIni = FileHelper.ini(
+ {
+ volumeId: 'xftp-configs',
+ subpath: './file-server.ini',
+ },
+ shape,
+)
diff --git a/startos/fileModels/ini-lib.ts b/startos/fileModels/ini-lib.ts
new file mode 100644
index 0000000..14ef897
--- /dev/null
+++ b/startos/fileModels/ini-lib.ts
@@ -0,0 +1,157 @@
+export type IniValue = string | number | boolean | string[]
+export type IniInputValue = IniValue | null | undefined
+export type IniSection = Record
+export type IniInputSection = Record
+export type IniData = Record
+export type IniInputData = Record
+
+export interface ParseOptions {
+ /** Delimiter for list values (default: ",") */
+ listDelimiter?: string
+ /** If true, keep all values as strings without type conversion */
+ rawStrings?: boolean
+}
+
+export interface StringifyOptions {
+ /** Delimiter for list values (default: ",") */
+ listDelimiter?: string
+}
+
+/**
+ * Parse a string value into its typed equivalent
+ */
+function parseValue(value: string, options: ParseOptions): IniValue {
+ if (options.rawStrings) {
+ return value
+ }
+
+ const trimmed = value.trim()
+
+ // Boolean
+ if (trimmed.toLowerCase() === 'true') return true
+ if (trimmed.toLowerCase() === 'false') return false
+
+ // Number (integers and floats, including negatives)
+ if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
+ const num = parseFloat(trimmed)
+ if (!isNaN(num)) return num
+ }
+
+ // List (contains delimiter)
+ const delimiter = options.listDelimiter ?? ','
+ if (trimmed.includes(delimiter)) {
+ return trimmed.split(delimiter).map((item) => item.trim())
+ }
+
+ // Default: string
+ return trimmed
+}
+
+/**
+ * Convert a typed value back to string
+ */
+function stringifyValue(value: IniValue, options: StringifyOptions): string {
+ if (Array.isArray(value)) {
+ const delimiter = options.listDelimiter ?? ','
+ return value.join(delimiter)
+ }
+ return String(value)
+}
+
+/**
+ * Check if a value is a section (nested object) vs a primitive value
+ */
+function isSection(
+ value: IniInputValue | IniInputSection,
+): value is IniInputSection {
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
+}
+
+/**
+ * Parse an INI string that uses `: ` as the key/value separator
+ */
+export function parse(content: string, options: ParseOptions = {}): IniData {
+ const result: IniData = {}
+ let currentSection: string | null = null
+
+ const lines = content.split(/\r?\n/)
+
+ for (const line of lines) {
+ const trimmed = line.trim()
+
+ // Skip empty lines and comments
+ if (!trimmed || trimmed.startsWith(';') || trimmed.startsWith('#')) {
+ continue
+ }
+
+ // Check for section header
+ const sectionMatch = trimmed.match(/^\[([^\]]+)\]$/)
+ if (sectionMatch) {
+ currentSection = sectionMatch[1]
+ result[currentSection] = {}
+ continue
+ }
+
+ // Parse key: value pairs
+ const separatorIndex = trimmed.indexOf(': ')
+ if (separatorIndex !== -1) {
+ const key = trimmed.slice(0, separatorIndex).trim()
+ const rawValue = trimmed.slice(separatorIndex + 2)
+ const value = parseValue(rawValue, options)
+
+ if (currentSection) {
+ ;(result[currentSection] as IniSection)[key] = value
+ } else {
+ result[key] = value
+ }
+ }
+ }
+
+ return result
+}
+
+/**
+ * Stringify an object to INI format using `: ` as the key/value separator
+ * Note: null and undefined values are omitted (INI has no native null concept)
+ */
+export function stringify(
+ data: IniInputData,
+ options: StringifyOptions = {},
+): string {
+ const lines: string[] = []
+
+ // First, output top-level key-value pairs (non-sections)
+ for (const [key, value] of Object.entries(data)) {
+ if (value == null) continue // filters both null and undefined
+ if (!isSection(value)) {
+ lines.push(`${key}: ${stringifyValue(value, options)}`)
+ }
+ }
+
+ // Then, output sections
+ for (const [key, value] of Object.entries(data)) {
+ if (value == null) continue
+ if (isSection(value)) {
+ const sectionLines: string[] = []
+ for (const [subKey, subValue] of Object.entries(value)) {
+ if (subValue == null) continue
+ sectionLines.push(`${subKey}: ${stringifyValue(subValue, options)}`)
+ }
+ // Only add section if it has values
+ if (sectionLines.length > 0) {
+ if (lines.length > 0) {
+ lines.push('') // Blank line before section
+ }
+ lines.push(`[${key}]`)
+ lines.push(...sectionLines)
+ }
+ }
+ }
+
+ return lines.join('\n')
+}
+
+/**
+ * Type guard to check if a value is a section
+ */
+export { isSection }
diff --git a/startos/fileModels/smp.ini.ts b/startos/fileModels/smp.ini.ts
deleted file mode 100644
index 745b8ec..0000000
--- a/startos/fileModels/smp.ini.ts
+++ /dev/null
@@ -1,146 +0,0 @@
-import { matches, FileHelper } from '@start9labs/start-sdk'
-import { smpConfigDefaults } from '../utils'
-
-const { object, string, oneOf, literal, natural, arrayOf } = matches
-
-const {
- information,
- store_log,
- auth,
- transport,
- proxy,
- inactive_clients,
- web,
-} = smpConfigDefaults
-
-const shape = object({
- information: object({
- // Basic info
- LICENSE: string.optional().onMismatch(information.LICENSE),
- source_code: literal(information.source_code).onMismatch(
- information.source_code,
- ),
-
- // Server usage conditions and amendments.
- usage_conditions: string
- .optional()
- .onMismatch(information.usage_conditions),
- condition_amendments: string
- .optional()
- .onMismatch(information.condition_amendments),
-
- // Server location and operator
- server_country: string.optional().onMismatch(information.server_country),
- operator: string.optional().onMismatch(information.operator),
- operator_country: string
- .optional()
- .onMismatch(information.operator_country),
- website: string.optional().onMismatch(information.website),
-
- // Administrative contacts
- admin_simplex: string.optional().onMismatch(information.admin_simplex), // SimpleX address
- admin_email: string.optional().onMismatch(information.admin_email),
- admin_pgp: string.optional().onMismatch(information.admin_pgp),
- admin_pgp_fingerprint: string
- .optional()
- .onMismatch(information.admin_pgp_fingerprint),
-
- // Contacts for complaints and feedback.
- complaints_simplex: string
- .optional()
- .onMismatch(information.complaints_simplex), // SimpleX address
- complaints_email: string
- .optional()
- .onMismatch(information.complaints_email),
- complaints_pgp: string.optional().onMismatch(information.complaints_pgp),
- complaints_pgp_fingerprint: string
- .optional()
- .onMismatch(information.complaints_pgp_fingerprint),
-
- // Hosting provider
- hosting: string.optional().onMismatch(information.hosting), // entity (organization or person name)
- hosting_country: string.optional().onMismatch(information.hosting_country), // ISO-3166 2-letter code
- }),
- store_log: object({
- enable: oneOf(literal('on'), literal('off')).onMismatch(store_log.enable),
-
- restore_messages: oneOf(literal('on'), literal('off')).onMismatch(
- store_log.restore_messages,
- ),
- expire_messages_days: natural.onMismatch(store_log.expire_messages_days),
- expire_ntfs_hours: natural.onMismatch(store_log.expire_ntfs_hours),
-
- log_stats: oneOf(literal('on'), literal('off')).onMismatch(
- store_log.log_stats,
- ),
- }),
- auth: object({
- new_queues: oneOf(literal('on'), literal('off')).onMismatch(
- auth.new_queues,
- ),
-
- create_password: string.onMismatch(auth.create_password),
-
- control_port_admin_password: string.onMismatch(
- auth.control_port_admin_password,
- ),
- control_port_user_password: string.onMismatch(
- auth.control_port_user_password,
- ),
- }),
- transport: object({
- host: literal(transport.host).onMismatch(transport.host),
- port: literal(transport.port).onMismatch(transport.port),
- log_tls_errors: oneOf(literal('on'), literal('off')).onMismatch(
- transport.log_tls_errors,
- ),
-
- websockets: oneOf(literal('on'), literal('off')).onMismatch(
- transport.websockets,
- ),
- control_port: literal(transport.control_port).onMismatch(
- transport.control_port,
- ),
- }),
- proxy: object({
- // Network configuration for SMP proxy client.
- host_mode: oneOf(literal('public'), literal('onion')).onMismatch(
- proxy.host_mode,
- ),
- required_host_mode: oneOf(literal('on'), literal('off')).onMismatch(
- proxy.required_host_mode,
- ),
-
- own_server_domains: arrayOf(string).onMismatch(proxy.own_server_domains),
-
- socks_proxy: string.onMismatch(proxy.socks_proxy),
- socks_mode: oneOf(literal('onion'), literal('always')).onMismatch(
- proxy.socks_mode,
- ),
-
- client_concurrency: natural.onMismatch(proxy.client_concurrency),
- }),
- inactive_clients: object({
- disconnect: oneOf(literal('on'), literal('off')).onMismatch(
- inactive_clients.disconnect,
- ),
- ttl: natural.onMismatch(inactive_clients.ttl),
- check_interval: natural.onMismatch(inactive_clients.check_interval),
- }),
- web: object({
- static_path: literal(web.static_path),
- http: literal(web.http).onMismatch(web.http),
-
- https: literal(web.https).onMismatch(web.https),
- cert: literal(web.cert).onMismatch(web.cert),
- key: literal(web.key).onMismatch(web.key),
- }),
-})
-
-export const smpConfigFile = FileHelper.ini(
- {
- volumeId: 'main',
- subpath: '/etc/opt/simplex/smp-server.ini',
- },
- shape,
-)
diff --git a/startos/fileModels/smpServer.ini.ts b/startos/fileModels/smpServer.ini.ts
new file mode 100644
index 0000000..2c77ed9
--- /dev/null
+++ b/startos/fileModels/smpServer.ini.ts
@@ -0,0 +1,146 @@
+import { matches, FileHelper } from '@start9labs/start-sdk'
+import { smpConfigDefaults, smpStatePath } from '../utils'
+import * as INI from './ini-lib'
+
+const { object, string, literal, literals, natural, nill } = matches
+
+const {
+ INFORMATION,
+ STORE_LOG,
+ AUTH,
+ TRANSPORT,
+ PROXY,
+ INACTIVE_CLIENTS,
+ WEB,
+} = smpConfigDefaults
+
+const shape = object({
+ INFORMATION: object({
+ source_code: literal(INFORMATION.source_code).onMismatch(
+ INFORMATION.source_code,
+ ),
+ }),
+ STORE_LOG: object({
+ /**
+ The server uses STM memory for persistence,
+ that will be lost on restart (e.g., as with redis).
+ This option enables saving memory to append only log,
+ and restoring it when the server is started.
+ Log is compacted on start (deleted objects are removed).
+ */
+ enable: literals('on', 'off').onMismatch(STORE_LOG.enable),
+ store_queues: literal(STORE_LOG.store_queues).onMismatch(
+ STORE_LOG.store_queues,
+ ),
+ store_messages: literal(STORE_LOG.store_messages).onMismatch(
+ STORE_LOG.store_messages,
+ ),
+ /**
+ Undelivered messages are optionally saved and restored when the server restarts,
+ they are preserved in the .bak file until the next restart.
+ */
+ restore_messages: literals('on', 'off').onMismatch(
+ STORE_LOG.restore_messages,
+ ),
+ expire_messages_days: natural
+ .optional()
+ .onMismatch(STORE_LOG.expire_messages_days),
+ expire_messages_on_start: literal(
+ STORE_LOG.expire_messages_on_start,
+ ).onMismatch(STORE_LOG.expire_messages_on_start),
+ expire_ntfs_hours: natural
+ .optional()
+ .onMismatch(STORE_LOG.expire_ntfs_hours),
+ /**
+ Log daily server statistics to CSV file
+ */
+ log_stats: literals('on', 'off').onMismatch(STORE_LOG.log_stats),
+ /**
+ Log interval for real-time Prometheus metrics, in seconds
+ */
+ prometheus_interval: natural
+ .optional()
+ .onMismatch(STORE_LOG.prometheus_interval),
+ }),
+ AUTH: object({
+ /**
+ Set new_queues option to off to completely prohibit creating new messaging queues.
+ This can be useful when you want to decommission the server, but not all connections are switched yet.
+ */
+ new_queues: literal(AUTH.new_queues).onMismatch(AUTH.new_queues),
+ /**
+ Use create_password option to enable basic auth to create new messaging queues.
+ The password should be used as part of server address in client configuration:
+ smp://fingerprint:password@host1,host2
+ The password will not be shared with the connecting contacts, you must share it only
+ with the users who you want to allow creating messaging queues on your server.
+ */
+ create_password: string,
+ control_port_admin_password: string
+ .optional()
+ .onMismatch(AUTH.control_port_admin_password),
+ control_port_user_password: string
+ .optional()
+ .onMismatch(AUTH.control_port_user_password),
+ }),
+ TRANSPORT: object({
+ /**
+ Host is only used to print server address on start.
+ You can specify multiple server ports.
+ */
+ host: string,
+ port: literal(TRANSPORT.port).onMismatch(TRANSPORT.port),
+ log_tls_errors: literal(TRANSPORT.log_tls_errors).onMismatch(
+ TRANSPORT.log_tls_errors,
+ ),
+ websockets: literal(TRANSPORT.websockets).onMismatch(TRANSPORT.websockets),
+ control_port: literal(TRANSPORT.control_port).onMismatch(
+ TRANSPORT.control_port,
+ ),
+ }),
+ PROXY: object({
+ socks_proxy: literal(PROXY.socks_proxy).onMismatch(PROXY.socks_proxy),
+ /**
+ Limit number of threads a client can spawn to process proxy commands in parallel.
+ */
+ client_concurrency: natural.onMismatch(PROXY.client_concurrency),
+ }),
+ INACTIVE_CLIENTS: object({
+ /**
+ Whether or not to disconnect inactive clients. If set, ttl and check_interval should also be set (seconds)
+ */
+ disconnect: literal(INACTIVE_CLIENTS.disconnect).onMismatch(
+ INACTIVE_CLIENTS.disconnect,
+ ),
+ }),
+ WEB: object({
+ /**
+ Set path to generate static mini-site for server information and qr codes/links
+ */
+ static_path: literal(`${smpStatePath}/www`)
+ .optional()
+ .onMismatch(WEB.static_path),
+ /**
+ Run an embedded server on this port
+ Onion sites can use any port and register it in the hidden service config.
+ Running on a port 80 may require setting process capabilities.
+ */
+ http: literal(WEB.http).onMismatch(WEB.http),
+ /**
+ Do not permit HTTPS, since that is handled by StartOS reverse proxy
+ */
+ https: nill.onMismatch(undefined),
+ cert: nill.onMismatch(undefined),
+ key: nill.onMismatch(undefined),
+ }),
+})
+
+export const smpServerIni = FileHelper.raw(
+ {
+ volumeId: 'smp-configs',
+ subpath: './smp-server.ini',
+ },
+ (inData) => INI.stringify(inData),
+ (inString) => INI.parse(inString),
+ (data) => shape.unsafeCast(data),
+)
diff --git a/startos/fileModels/xftp.ini.ts b/startos/fileModels/xftp.ini.ts
deleted file mode 100644
index 6102a08..0000000
--- a/startos/fileModels/xftp.ini.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { matches, FileHelper } from '@start9labs/start-sdk'
-const { object } = matches
-
-// @TODO
-const shape = object({})
-
-export const xftpConfigFile = FileHelper.ini(
- {
- volumeId: 'main',
- subpath: '/etc/opt/simplex/xftp-server.ini',
- },
- shape,
-)
diff --git a/startos/init/index.ts b/startos/init/index.ts
index f69244d..8728100 100644
--- a/startos/init/index.ts
+++ b/startos/init/index.ts
@@ -4,6 +4,7 @@ import { setInterfaces } from '../interfaces'
import { versionGraph } from '../install/versionGraph'
import { actions } from '../actions'
import { restoreInit } from '../backups'
+import { initServers } from './initServers'
export const init = sdk.setupInit(
restoreInit,
@@ -11,6 +12,7 @@ export const init = sdk.setupInit(
setInterfaces,
setDependencies,
actions,
+ initServers,
)
export const uninit = sdk.setupUninit(versionGraph)
diff --git a/startos/init/initServers.ts b/startos/init/initServers.ts
new file mode 100644
index 0000000..fd69f80
--- /dev/null
+++ b/startos/init/initServers.ts
@@ -0,0 +1,64 @@
+import { utils } from '@start9labs/start-sdk'
+import { sdk } from '../sdk'
+import {
+ smpConfigDefaults,
+ smpMounts,
+ xftpConfigDefaults,
+ xftpFilePath,
+ xftpMounts,
+} from '../utils'
+import { smpServerIni } from '../fileModels/smpServer.ini'
+import { fileServerIni } from '../fileModels/fileServer.ini'
+
+export const initServers = sdk.setupOnInit(async (effects, kind) => {
+ if (kind !== 'install') return
+
+ const create_password = utils.getDefaultString({
+ charset: 'a-z,A-Z,1-9,!,$,%,&,*',
+ len: 21,
+ })
+
+ await sdk.SubContainer.withTemp(
+ effects,
+ { imageId: 'smp' },
+ smpMounts,
+ 'init-smp',
+ async (sub) => {
+ await sub.execFail([
+ 'smp-server',
+ 'init',
+ '-y',
+ '-l',
+ '--password',
+ create_password,
+ ])
+
+ await smpServerIni.write(effects, {
+ ...smpConfigDefaults,
+ AUTH: { ...smpConfigDefaults.AUTH, create_password },
+ })
+ },
+ )
+
+ await sdk.SubContainer.withTemp(
+ effects,
+ { imageId: 'xftp' },
+ xftpMounts,
+ 'init-xftp',
+ async (sub) => {
+ await sub.execFail([
+ 'xftp-server',
+ 'init',
+ '-p',
+ xftpFilePath,
+ '-q',
+ xftpConfigDefaults.FILES.storage_quota,
+ ])
+
+ await fileServerIni.write(effects, {
+ ...xftpConfigDefaults,
+ AUTH: { ...xftpConfigDefaults.AUTH, create_password },
+ })
+ },
+ )
+})
diff --git a/startos/install/versionGraph.ts b/startos/install/versionGraph.ts
index 3204c2c..1016a54 100644
--- a/startos/install/versionGraph.ts
+++ b/startos/install/versionGraph.ts
@@ -1,23 +1,8 @@
-import { utils, VersionGraph } from '@start9labs/start-sdk'
+import { VersionGraph } from '@start9labs/start-sdk'
import { current, other } from './versions'
-import { randomPassword, smpConfigDefaults } from '../utils'
-import { smpConfigFile } from '../fileModels/smp.ini'
-import { sdk } from '../sdk'
export const versionGraph = VersionGraph.of({
current,
other,
- preInstall: async (effects) => {
- await smpConfigFile.write(effects, {
- ...smpConfigDefaults,
- auth: {
- ...smpConfigDefaults.auth,
- create_password: utils.getDefaultString(randomPassword),
- },
- proxy: {
- ...smpConfigDefaults.proxy,
- socks_proxy: `${await sdk.getOsIp(effects)}:9050`,
- },
- })
- },
+ preInstall: async (effects) => {},
})
diff --git a/startos/install/versions/index.ts b/startos/install/versions/index.ts
index 7c3e78a..11e9b4d 100644
--- a/startos/install/versions/index.ts
+++ b/startos/install/versions/index.ts
@@ -1,2 +1,2 @@
-export { v_6_3_2_1 as current } from './v6.3.2.1'
+export { v_6_4_5_0_a0 as current } from './v6.4.5.0.a0'
export const other = []
diff --git a/startos/install/versions/v6.3.2.1.ts b/startos/install/versions/v6.3.2.1.ts
deleted file mode 100644
index 80efbd1..0000000
--- a/startos/install/versions/v6.3.2.1.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { VersionInfo, IMPOSSIBLE } from '@start9labs/start-sdk'
-import { rm } from 'fs/promises'
-
-export const v_6_3_2_1 = VersionInfo.of({
- version: '6.3.2:1',
- releaseNotes: 'Revamped for StartOS 0.4.0',
- migrations: {
- up: async ({ effects }) => {
- await rm('/media/startos/volumes/main/start9', { recursive: true }).catch(
- console.error,
- )
- },
- down: IMPOSSIBLE,
- },
-})
diff --git a/startos/install/versions/v6.4.5.0.a0.ts b/startos/install/versions/v6.4.5.0.a0.ts
new file mode 100644
index 0000000..7cffdf5
--- /dev/null
+++ b/startos/install/versions/v6.4.5.0.a0.ts
@@ -0,0 +1,121 @@
+import { VersionInfo, IMPOSSIBLE, YAML, utils } from '@start9labs/start-sdk'
+import { execFile } from 'child_process'
+import { readdir, readFile, rm } from 'fs/promises'
+import { smpServerIni } from '../../fileModels/smpServer.ini'
+import { smpConfigDefaults, xftpConfigDefaults } from '../../utils'
+import { fileServerIni } from '../../fileModels/fileServer.ini'
+import { join } from 'path'
+
+// NOTE, adding passwords to xftp server addresses. Previous addresses are less secure and expected to break.
+
+export const v_6_4_5_0_a0 = VersionInfo.of({
+ version: '6.4.5:0-alpha.0',
+ releaseNotes: 'Updated for StartOS 0.4.0',
+ migrations: {
+ up: async ({ effects }) => {
+ // get old stats.yaml
+ const statsYaml:
+ | {
+ data: {
+ 'SimpleX SMP Server Address': {
+ value: string
+ }
+ }
+ }
+ | undefined = await readFile(
+ '/media/startos/volumes/main/start9/stats.yaml',
+ 'utf-8',
+ ).then(YAML.parse, () => undefined)
+
+ if (statsYaml) {
+ // config (was used for smp-server.ini)
+ await new Promise((res, rej) => {
+ execFile(
+ 'sh',
+ [
+ '-c',
+ 'mv /media/startos/volumes/conf/* /media/startos/volumes/smp-configs',
+ ],
+ (err) => (err ? rej(err) : res(null)),
+ )
+ }).catch(console.error)
+
+ // xftp (was used for file-server.ini)
+ await new Promise((res, rej) => {
+ execFile(
+ 'sh',
+ [
+ '-c',
+ 'mv /media/startos/volumes/xftp/* /media/startos/volumes/xftp-configs',
+ ],
+ (err) => (err ? rej(err) : res(null)),
+ )
+ }).catch(console.error)
+
+ // log (was used for smp-state)
+ await new Promise((res, rej) => {
+ execFile(
+ 'sh',
+ [
+ '-c',
+ 'mv /media/startos/volumes/log/* /media/startos/volumes/smp-state',
+ ],
+ (err) => (err ? rej(err) : res(null)),
+ )
+ }).catch(console.error)
+
+ // main (was used for xftp files)
+ await new Promise((res, rej) => {
+ execFile(
+ 'sh',
+ [
+ '-c',
+ 'mv /media/startos/volumes/xftp/* /media/startos/volumes/xftp-files',
+ ],
+ (err) => (err ? rej(err) : res(null)),
+ )
+ }).catch(console.error)
+
+ const create_password =
+ new URL(
+ statsYaml.data['SimpleX SMP Server Address'].value.replace(
+ 'smp://',
+ 'https://',
+ ),
+ ).password ||
+ utils.getDefaultString({
+ charset: 'a-z,A-Z,1-9,!,$,%,&,*',
+ len: 21,
+ })
+
+ // overwrite smp-server.ini
+ await smpServerIni.write(effects, {
+ ...smpConfigDefaults,
+ AUTH: { ...smpConfigDefaults.AUTH, create_password },
+ })
+
+ // overwrite file-server.ini
+ await fileServerIni.write(effects, {
+ ...xftpConfigDefaults,
+ AUTH: { ...xftpConfigDefaults.AUTH, create_password },
+ })
+ // remove everything from old volumes
+ await Promise.all(
+ ['conf', 'xftp', 'log', 'main'].map((vol) =>
+ clearVolume(`/media/startos/volumes/${vol}`),
+ ),
+ )
+ }
+ },
+ down: IMPOSSIBLE,
+ },
+})
+
+async function clearVolume(volumePath: string) {
+ const entries = await readdir(volumePath)
+ await Promise.all(
+ entries.map((entry) =>
+ rm(join(volumePath, entry), { recursive: true }).catch(console.error),
+ ),
+ )
+}
diff --git a/startos/interfaces.ts b/startos/interfaces.ts
index 300a42f..2f6ba18 100644
--- a/startos/interfaces.ts
+++ b/startos/interfaces.ts
@@ -1,61 +1,93 @@
+import { FileHelper } from '@start9labs/start-sdk'
import { sdk } from './sdk'
import { smpPort, webPort, xftpPort } from './utils'
+import { smpServerIni } from './fileModels/smpServer.ini'
export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => {
- // ** SMP Server **
- const smpMulti = sdk.MultiHost.of(effects, 'smp-multi')
+ // get fingerprints
+ const smpFingerprint = await FileHelper.string({
+ volumeId: 'smp-configs',
+ subpath: './fingerprint',
+ })
+ .read()
+ .const(effects)
+ .then((f) => f?.trim())
+
+ const xftpFingerprint = await FileHelper.string({
+ volumeId: 'xftp-configs',
+ subpath: './fingerprint',
+ })
+ .read()
+ .const(effects)
+ .then((f) => f?.trim())
- // smp
+ // get password
+ const password = await smpServerIni
+ .read((s) => s.AUTH.create_password)
+ .const(effects)
+
+ // ** SMP Server **
+ const smpMulti = sdk.MultiHost.of(effects, 'main')
const smpMultiOrigin = await smpMulti.bindPort(smpPort, {
- protocol: 'http',
+ protocol: null,
+ addSsl: null,
+ preferredExternalPort: smpPort,
+ secure: { ssl: true },
})
const smp = sdk.createInterface(effects, {
name: 'SMP Server',
id: 'smp',
description: 'The SMP server for SimpleX',
type: 'api',
- masked: false,
- schemeOverride: null,
- username: null,
+ masked: true,
+ schemeOverride: { ssl: 'smp', noSsl: 'smp' },
+ username: `${smpFingerprint}:${password}`,
path: '',
query: {},
})
const smpReceipt = await smpMultiOrigin.export([smp])
- // admin
- const adminMultiOrigin = await smpMulti.bindPort(webPort, {
- protocol: 'http',
- })
- const admin = sdk.createInterface(effects, {
- name: 'Admin UI',
- id: 'admin',
- description: 'Your SimpleX SMP server admin user interface',
- type: 'ui',
- masked: false,
- schemeOverride: null,
- username: null,
- path: '',
- query: {},
- })
- const adminReceipt = await adminMultiOrigin.export([admin])
-
// ** XFTP Server **
- const xftpMulti = sdk.MultiHost.of(effects, 'xftp-multi')
+ const xftpMulti = sdk.MultiHost.of(effects, 'xftp')
const xftpMultiOrigin = await xftpMulti.bindPort(xftpPort, {
- protocol: 'http',
+ protocol: null,
+ addSsl: null,
+ preferredExternalPort: xftpPort,
+ secure: { ssl: true },
})
const xftp = sdk.createInterface(effects, {
name: 'XFTP Server',
id: 'xftp',
description: 'The XFTP server for SimpleX',
type: 'api',
- masked: false,
- schemeOverride: null,
- username: null,
+ masked: true,
+ schemeOverride: { ssl: 'xftp', noSsl: 'xftp' },
+ username: `${xftpFingerprint}:${password}`,
path: '',
query: {},
})
const xftpReceipt = await xftpMultiOrigin.export([xftp])
- return [smpReceipt, adminReceipt, xftpReceipt]
+ const receipts = [smpReceipt, xftpReceipt]
+
+ // @TODO consider implementing optional web info page
+ // if (store.enableWeb) {
+ // const infoMultiOrigin = await smpMulti.bindPort(webPort, {
+ // protocol: 'http',
+ // })
+ // const info = sdk.createInterface(effects, {
+ // name: 'Info UI',
+ // id: 'info',
+ // description: 'Information about your SMP server',
+ // type: 'ui',
+ // masked: false,
+ // schemeOverride: null,
+ // username: null,
+ // path: '',
+ // query: {},
+ // })
+ // receipts.push(await infoMultiOrigin.export([info]))
+ // }
+
+ return receipts
})
diff --git a/startos/main.ts b/startos/main.ts
index 79765e7..9f7b8cf 100644
--- a/startos/main.ts
+++ b/startos/main.ts
@@ -1,6 +1,7 @@
+import { fileServerIni } from './fileModels/fileServer.ini'
+import { smpServerIni } from './fileModels/smpServer.ini'
import { sdk } from './sdk'
-import { T } from '@start9labs/start-sdk'
-import { smpPort, xftpPort } from './utils'
+import { smpMounts, smpPort, xftpMounts, xftpPort } from './utils'
export const main = sdk.setupMain(async ({ effects }) => {
/**
@@ -8,19 +9,17 @@ export const main = sdk.setupMain(async ({ effects }) => {
*
* In this section, we fetch any resources or run any desired preliminary commands.
*/
- console.info('[i] Starting SimpleX!')
+ console.info('Starting SimpleX!')
- const simplexSub = await sdk.SubContainer.of(
- effects,
- { imageId: 'simplex' },
- sdk.Mounts.of().mountVolume({
- volumeId: 'main',
- subpath: null,
- mountpoint: '/data',
- readonly: false,
- }),
- 'simplex-sub',
- )
+ // confirm configuration files are present and restart service if they change
+ const smpIni = await smpServerIni.read().const(effects)
+ if (!smpIni) {
+ throw new Error('No smp-server.ini')
+ }
+ const fileIni = await fileServerIni.read().const(effects)
+ if (!fileIni) {
+ throw new Error('No file-server.ini')
+ }
/**
* ======================== Daemons ========================
@@ -31,8 +30,15 @@ export const main = sdk.setupMain(async ({ effects }) => {
*/
return sdk.Daemons.of(effects)
.addDaemon('smp', {
- subcontainer: simplexSub,
- exec: { command: ['smp-server', 'start', '+RTS', '-N', '-RTS'] },
+ subcontainer: await sdk.SubContainer.of(
+ effects,
+ { imageId: 'smp' },
+ smpMounts,
+ 'smp-sub',
+ ),
+ exec: {
+ command: sdk.useEntrypoint(),
+ },
ready: {
display: 'SMP Server',
fn: () =>
@@ -44,8 +50,15 @@ export const main = sdk.setupMain(async ({ effects }) => {
requires: [],
})
.addDaemon('xftp', {
- subcontainer: simplexSub,
- exec: { command: ['xftp-server', 'start', '+RTS', '-N', '-RTS'] },
+ subcontainer: await sdk.SubContainer.of(
+ effects,
+ { imageId: 'xftp' },
+ xftpMounts,
+ 'xftp-sub',
+ ),
+ exec: {
+ command: sdk.useEntrypoint(),
+ },
ready: {
display: 'XFTP Server',
fn: () =>
diff --git a/startos/manifest.ts b/startos/manifest.ts
index 4e7ef1a..37fe397 100644
--- a/startos/manifest.ts
+++ b/startos/manifest.ts
@@ -1,28 +1,41 @@
import { setupManifest } from '@start9labs/start-sdk'
-import { source_code } from './utils'
export const manifest = setupManifest({
id: 'simplex',
title: 'SimpleX Server',
license: 'MIT',
wrapperRepo: 'https://github.com/Start9Labs/simplex-startos',
- upstreamRepo: source_code,
- supportSite: 'https://github.com/simplex-chat/simplexmq/issues',
+ upstreamRepo: 'https://github.com/simplex-chat/simplexmq/',
+ supportSite: 'https://github.com/simplex-chat/simplexmq//issues',
marketingSite: 'https://simplex.chat/',
- docsUrl:
- 'https://github.com/Start9Labs/simplex-startos/blob/update/040/docs/README.md',
+ docsUrl:
+ 'https://github.com/Start9Labs/simplex-startos/blob/update/040/docs/instructions.md',
donationUrl:
'https://github.com/simplex-chat/simplex-chat#help-us-with-donations',
- // @TODO
description: {
- short: '',
- long: '',
+ short: 'Freedom & security of your communications',
+ long: 'An open-source, decentralized messenger designed for maximum privacy and control.',
},
- volumes: ['main'],
+ volumes: [
+ 'smp-configs',
+ 'smp-state',
+ 'xftp-configs',
+ 'xftp-state',
+ 'xftp-files',
+ 'main', // migration
+ 'conf', // migration
+ 'xftp', // migration
+ 'log', // migration
+ ],
images: {
- simplex: {
+ smp: {
source: {
- dockerBuild: {},
+ dockerTag: 'simplexchat/smp-server:v6.4.5',
+ },
+ },
+ xftp: {
+ source: {
+ dockerTag: 'simplexchat/xftp-server:v6.4.5',
},
},
},
diff --git a/startos/utils.ts b/startos/utils.ts
index 8760965..e1968d6 100644
--- a/startos/utils.ts
+++ b/startos/utils.ts
@@ -1,95 +1,116 @@
-export const smpPort = 5223
-export const xftpPort = 443
-export const controlPort = 5224
-export const webPort = 443
-
-export const source_code = 'https://github.com/simplex-chat/simplexmq/'
+import { sdk } from './sdk'
-export const randomPassword = {
- charset: 'a-z,A-Z,1-9,!,$,%,&,*',
- len: 21,
-}
+export const smpPort = 5223
+export const smpControlPort = 5224
+
+export const xftpPort = 5225
+export const xftpControlPort = 5226
+
+export const smpStatePath = '/var/opt/simplex'
+export const xftpFilePath = '/srv/xftp'
+
+export const webPort = 8000
+
+export const smpMounts = sdk.Mounts.of()
+ .mountVolume({
+ volumeId: 'smp-configs',
+ subpath: null,
+ mountpoint: '/etc/opt/simplex',
+ readonly: false,
+ })
+ .mountVolume({
+ volumeId: 'smp-state',
+ subpath: null,
+ mountpoint: smpStatePath,
+ readonly: false,
+ })
+
+export const xftpMounts = sdk.Mounts.of()
+ .mountVolume({
+ volumeId: 'xftp-configs',
+ subpath: null,
+ mountpoint: '/etc/opt/simplex-xftp',
+ readonly: false,
+ })
+ .mountVolume({
+ volumeId: 'xftp-state',
+ subpath: null,
+ mountpoint: '/var/opt/simplex-xftp',
+ readonly: false,
+ })
+ .mountVolume({
+ volumeId: 'xftp-files',
+ subpath: null,
+ mountpoint: xftpFilePath,
+ readonly: false,
+ })
export const smpConfigDefaults = {
- information: {
- // Basic info
- LICENSE: undefined,
- source_code,
-
- // Server usage conditions and amendments.
- usage_conditions: undefined,
- condition_amendments: undefined,
-
- // Server location and operator
- server_country: undefined,
- operator: undefined,
- operator_country: undefined,
- website: undefined,
-
- // Administrative contacts
- admin_simplex: undefined, // SimpleX address
- admin_email: undefined,
- admin_pgp: undefined,
- admin_pgp_fingerprint: undefined,
-
- // Contacts for complaints and feedback.
- complaints_simplex: undefined, // SimpleX address
- complaints_email: undefined,
- complaints_pgp: undefined,
- complaints_pgp_fingerprint: undefined,
-
- // Hosting provider
- hosting: undefined, // entity organization or person name
- hosting_country: undefined, // ISO-3166 2-letter code
+ INFORMATION: {
+ source_code: 'https://github.com/simplex-chat/simplexmq/',
},
- store_log: {
- enable: 'on' as const,
-
- restore_messages: 'on' as const,
- expire_messages_days: 21,
- expire_ntfs_hours: 24,
-
- log_stats: 'on' as const,
+ STORE_LOG: {
+ enable: 'on',
+ store_queues: 'memory',
+ store_messages: 'memory',
+ restore_messages: 'on',
+ expire_messages_days: 365, // @TODO ask Evgany
+ expire_messages_on_start: 'off',
+ expire_ntfs_hours: 168,
+ log_stats: 'on',
+ prometheus_interval: undefined,
},
- auth: {
- new_queues: 'on' as const,
-
- create_password: '', // any printable ASCII characters without whitespace, '@', ':' and '/'
-
- control_port_admin_password: '',
- control_port_user_password: '',
+ AUTH: {
+ new_queues: 'on',
+ control_port_admin_password: undefined,
+ control_port_user_password: undefined,
},
- transport: {
- host: 'hostnames', //
- port: 5223,
- log_tls_errors: 'off' as const,
-
- websockets: 'off' as const,
- control_port: controlPort,
+ TRANSPORT: {
+ host: '',
+ port: `${smpPort},443`,
+ log_tls_errors: 'off',
+ websockets: 'off',
+ control_port: smpControlPort,
},
- proxy: {
- // Network configuration for SMP proxy client.
- host_mode: 'public' as const,
- required_host_mode: 'off' as const,
-
- own_server_domains: [],
-
- socks_proxy: '',
- socks_mode: 'onion' as const,
-
+ PROXY: {
+ socks_proxy: '127.0.0.1:9050',
client_concurrency: 32,
},
- inactive_clients: {
- disconnect: 'off' as const,
- ttl: 21600,
- check_interval: 3600,
+ INACTIVE_CLIENTS: {
+ disconnect: 'off',
},
- web: {
- static_path: 'var/opt/simplex/www',
- http: undefined,
-
- https: webPort,
- cert: '', // @TODO Aiden
- key: '', // @TODO Aiden
+ WEB: {
+ static_path: undefined,
+ http: webPort,
+ https: undefined,
+ cert: undefined,
+ key: undefined,
+ },
+} as const
+
+export const xftpConfigDefaults = {
+ STORE_LOG: {
+ enable: 'on',
+ expire_files_hours: 168,
+ log_stats: 'off',
+ prometheus_interval: undefined,
+ },
+ AUTH: {
+ new_files: 'on',
+ control_port_admin_password: undefined,
+ control_port_user_password: undefined,
+ },
+ TRANSPORT: {
+ host: '',
+ port: xftpPort,
+ log_tls_errors: 'off',
+ control_port: xftpControlPort,
+ },
+ FILES: {
+ path: xftpFilePath,
+ storage_quota: '10gb',
+ },
+ INACTIVE_CLIENTS: {
+ disconnect: 'off',
},
-}
+} as const
From d0fd2f588697fabafca49daa60905c0a1ebb3244 Mon Sep 17 00:00:00 2001
From: Matt Hill
Date: Wed, 14 Jan 2026 16:56:07 -0700
Subject: [PATCH 15/35] beta 47
---
package-lock.json | 8 ++++----
package.json | 2 +-
startos/fileModels/fileServer.ini.ts | 3 ++-
startos/fileModels/smpServer.ini.ts | 3 ++-
startos/interfaces.ts | 4 ++--
5 files changed, 11 insertions(+), 9 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 4e9ba96..0526b0a 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.45",
+ "@start9labs/start-sdk": "^0.4.0-beta.47",
"tldts": "^7.0.19"
},
"devDependencies": {
@@ -50,9 +50,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.47",
+ "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.47.tgz",
+ "integrity": "sha512-GhnKJ8F/aEuW4gu0GxHXcSDrj69+H+Dysr0a743q7Hy2HZQSvndAy0B3cPmqz+0oaHKXnCL5r8nicgH8zXlENw==",
"license": "MIT",
"dependencies": {
"@iarna/toml": "^3.0.0",
diff --git a/package.json b/package.json
index bb81a25..449bb69 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.47",
"tldts": "^7.0.19"
},
"devDependencies": {
diff --git a/startos/fileModels/fileServer.ini.ts b/startos/fileModels/fileServer.ini.ts
index c940d3d..cb05b8c 100644
--- a/startos/fileModels/fileServer.ini.ts
+++ b/startos/fileModels/fileServer.ini.ts
@@ -1,5 +1,6 @@
import { matches, FileHelper } from '@start9labs/start-sdk'
import { xftpConfigDefaults } from '../utils'
+import { sdk } from '../sdk'
const { object, literals, natural, string, literal } = matches
@@ -77,7 +78,7 @@ const shape = object({
export const fileServerIni = FileHelper.ini(
{
- volumeId: 'xftp-configs',
+ base: sdk.volumes['xftp-configs'],
subpath: './file-server.ini',
},
shape,
diff --git a/startos/fileModels/smpServer.ini.ts b/startos/fileModels/smpServer.ini.ts
index 2c77ed9..a517b0f 100644
--- a/startos/fileModels/smpServer.ini.ts
+++ b/startos/fileModels/smpServer.ini.ts
@@ -1,6 +1,7 @@
import { matches, FileHelper } from '@start9labs/start-sdk'
import { smpConfigDefaults, smpStatePath } from '../utils'
import * as INI from './ini-lib'
+import { sdk } from '../sdk'
const { object, string, literal, literals, natural, nill } = matches
@@ -137,7 +138,7 @@ const shape = object({
export const smpServerIni = FileHelper.raw(
{
- volumeId: 'smp-configs',
+ base: sdk.volumes['smp-configs'],
subpath: './smp-server.ini',
},
(inData) => INI.stringify(inData),
diff --git a/startos/interfaces.ts b/startos/interfaces.ts
index 2f6ba18..2e66a33 100644
--- a/startos/interfaces.ts
+++ b/startos/interfaces.ts
@@ -6,7 +6,7 @@ import { smpServerIni } from './fileModels/smpServer.ini'
export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => {
// get fingerprints
const smpFingerprint = await FileHelper.string({
- volumeId: 'smp-configs',
+ base: sdk.volumes['smp-configs'],
subpath: './fingerprint',
})
.read()
@@ -14,7 +14,7 @@ export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => {
.then((f) => f?.trim())
const xftpFingerprint = await FileHelper.string({
- volumeId: 'xftp-configs',
+ base: sdk.volumes['xftp-configs'],
subpath: './fingerprint',
})
.read()
From 56d3f1931aa97cf05c55c56bfa7c2fe2cec0dfec Mon Sep 17 00:00:00 2001
From: Matt Hill
Date: Sun, 1 Feb 2026 14:37:19 -0700
Subject: [PATCH 16/35] beta 48, i18n, and more
---
.github/workflows/buildService.yml | 7 ++
.github/workflows/releaseService.yml | 1 +
Makefile | 92 +---------------
package-lock.json | 9 +-
package.json | 2 +-
s9pk.mk | 103 ++++++++++++++++++
startos/i18n/dictionaries/default.ts | 19 ++++
startos/i18n/dictionaries/translations.ts | 56 ++++++++++
startos/i18n/index.ts | 5 +
startos/install/versions/index.ts | 2 +-
.../{v6.4.5.0.a0.ts => v7.0.0.0.b0.ts} | 12 +-
startos/interfaces.ts | 9 +-
startos/main.ts | 19 ++--
startos/manifest/i18n.ts | 18 +++
startos/{manifest.ts => manifest/index.ts} | 12 +-
15 files changed, 248 insertions(+), 118 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/{v6.4.5.0.a0.ts => v7.0.0.0.b0.ts} (91%)
create mode 100644 startos/manifest/i18n.ts
rename startos/{manifest.ts => manifest/index.ts} (76%)
diff --git a/.github/workflows/buildService.yml b/.github/workflows/buildService.yml
index 5d19b77..feb2ae9 100644
--- a/.github/workflows/buildService.yml
+++ b/.github/workflows/buildService.yml
@@ -9,8 +9,15 @@ 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
+ # with:
+ # FREE_DISK_SPACE: true
secrets:
DEV_KEY: ${{ secrets.DEV_KEY }}
diff --git a/.github/workflows/releaseService.yml b/.github/workflows/releaseService.yml
index 4764532..ecc6a66 100644
--- a/.github/workflows/releaseService.yml
+++ b/.github/workflows/releaseService.yml
@@ -9,6 +9,7 @@ jobs:
release:
uses: start9labs/shared-workflows/.github/workflows/releaseService.yml@master
with:
+ # FREE_DISK_SPACE: true
REGISTRY: ${{ vars.REGISTRY }} # Optional. Defaults to https://alpha-registry-x.start9.com
secrets:
DEV_KEY: ${{ secrets.DEV_KEY }} # Required
diff --git a/Makefile b/Makefile
index 295e219..10d4360 100644
--- a/Makefile
+++ b/Makefile
@@ -1,89 +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 '[.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: $(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..."
- @rm -rf $(PACKAGE_ID).s9pk $(PACKAGE_ID)_x86_64.s9pk $(PACKAGE_ID)_aarch64.s9pk $(PACKAGE_ID)_riscv64.s9pk javascript node_modules
+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 0526b0a..854177a 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.47",
+ "@start9labs/start-sdk": "0.4.0-beta.48",
"tldts": "^7.0.19"
},
"devDependencies": {
@@ -50,10 +50,9 @@
}
},
"node_modules/@start9labs/start-sdk": {
- "version": "0.4.0-beta.47",
- "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.47.tgz",
- "integrity": "sha512-GhnKJ8F/aEuW4gu0GxHXcSDrj69+H+Dysr0a743q7Hy2HZQSvndAy0B3cPmqz+0oaHKXnCL5r8nicgH8zXlENw==",
- "license": "MIT",
+ "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==",
"dependencies": {
"@iarna/toml": "^3.0.0",
"@noble/curves": "^1.8.2",
diff --git a/package.json b/package.json
index 449bb69..2a77d83 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
"check": "tsc --noEmit"
},
"dependencies": {
- "@start9labs/start-sdk": "^0.4.0-beta.47",
+ "@start9labs/start-sdk": "0.4.0-beta.48",
"tldts": "^7.0.19"
},
"devDependencies": {
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/i18n/dictionaries/default.ts b/startos/i18n/dictionaries/default.ts
new file mode 100644
index 0000000..dbb713e
--- /dev/null
+++ b/startos/i18n/dictionaries/default.ts
@@ -0,0 +1,19 @@
+export const DEFAULT_LANG = 'en_US'
+
+const dict = {
+ 'Starting SimpleX!': 0,
+ 'No smp-server.ini': 1,
+ 'No file-server.ini': 2,
+ 'SMP Server': 3,
+ 'The SMP server is ready': 4,
+ 'The SMP server is not ready': 5,
+ 'XFTP Server': 6,
+ 'The XFTP server is ready': 7,
+ 'The XFTP server is not ready': 8,
+ 'The SMP server for SimpleX': 9,
+ 'The XFTP server for SimpleX': 10,
+} as const
+
+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..d66b53e
--- /dev/null
+++ b/startos/i18n/dictionaries/translations.ts
@@ -0,0 +1,56 @@
+import { LangDict } from './default'
+
+export default {
+ es_ES: {
+ 0: '¡Iniciando SimpleX!',
+ 1: 'Sin smp-server.ini',
+ 2: 'Sin file-server.ini',
+ 3: 'Servidor SMP',
+ 4: 'El servidor SMP está listo',
+ 5: 'El servidor SMP no está listo',
+ 6: 'Servidor XFTP',
+ 7: 'El servidor XFTP está listo',
+ 8: 'El servidor XFTP no está listo',
+ 9: 'El servidor SMP de SimpleX',
+ 10: 'El servidor XFTP de SimpleX',
+ },
+ de_DE: {
+ 0: 'SimpleX wird gestartet!',
+ 1: 'Keine smp-server.ini',
+ 2: 'Keine file-server.ini',
+ 3: 'SMP-Server',
+ 4: 'Der SMP-Server ist bereit',
+ 5: 'Der SMP-Server ist nicht bereit',
+ 6: 'XFTP-Server',
+ 7: 'Der XFTP-Server ist bereit',
+ 8: 'Der XFTP-Server ist nicht bereit',
+ 9: 'Der SMP-Server für SimpleX',
+ 10: 'Der XFTP-Server für SimpleX',
+ },
+ pl_PL: {
+ 0: 'Uruchamianie SimpleX!',
+ 1: 'Brak smp-server.ini',
+ 2: 'Brak file-server.ini',
+ 3: 'Serwer SMP',
+ 4: 'Serwer SMP jest gotowy',
+ 5: 'Serwer SMP nie jest gotowy',
+ 6: 'Serwer XFTP',
+ 7: 'Serwer XFTP jest gotowy',
+ 8: 'Serwer XFTP nie jest gotowy',
+ 9: 'Serwer SMP dla SimpleX',
+ 10: 'Serwer XFTP dla SimpleX',
+ },
+ fr_FR: {
+ 0: 'Démarrage de SimpleX !',
+ 1: 'Pas de smp-server.ini',
+ 2: 'Pas de file-server.ini',
+ 3: 'Serveur SMP',
+ 4: 'Le serveur SMP est prêt',
+ 5: 'Le serveur SMP n\'est pas prêt',
+ 6: 'Serveur XFTP',
+ 7: 'Le serveur XFTP est prêt',
+ 8: 'Le serveur XFTP n\'est pas prêt',
+ 9: 'Le serveur SMP de SimpleX',
+ 10: 'Le serveur XFTP de SimpleX',
+ },
+} satisfies Record
diff --git a/startos/i18n/index.ts b/startos/i18n/index.ts
new file mode 100644
index 0000000..1849d6d
--- /dev/null
+++ b/startos/i18n/index.ts
@@ -0,0 +1,5 @@
+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/install/versions/index.ts b/startos/install/versions/index.ts
index 11e9b4d..7ae0e45 100644
--- a/startos/install/versions/index.ts
+++ b/startos/install/versions/index.ts
@@ -1,2 +1,2 @@
-export { v_6_4_5_0_a0 as current } from './v6.4.5.0.a0'
+export { v_7_0_0_0_b0 as current } from './v7.0.0.0.b0'
export const other = []
diff --git a/startos/install/versions/v6.4.5.0.a0.ts b/startos/install/versions/v7.0.0.0.b0.ts
similarity index 91%
rename from startos/install/versions/v6.4.5.0.a0.ts
rename to startos/install/versions/v7.0.0.0.b0.ts
index 7cffdf5..3f44526 100644
--- a/startos/install/versions/v6.4.5.0.a0.ts
+++ b/startos/install/versions/v7.0.0.0.b0.ts
@@ -8,9 +8,15 @@ import { join } from 'path'
// NOTE, adding passwords to xftp server addresses. Previous addresses are less secure and expected to break.
-export const v_6_4_5_0_a0 = VersionInfo.of({
- version: '6.4.5:0-alpha.0',
- releaseNotes: 'Updated for StartOS 0.4.0',
+export const v_7_0_0_0_b0 = VersionInfo.of({
+ version: '7.0.0:0-beta.0',
+ releaseNotes: {
+ en_US: 'Updated for StartOS 0.4.0',
+ es_ES: 'Actualizado para StartOS 0.4.0',
+ de_DE: 'Aktualisiert für StartOS 0.4.0',
+ pl_PL: 'Zaktualizowano dla StartOS 0.4.0',
+ fr_FR: 'Mis à jour pour StartOS 0.4.0',
+ },
migrations: {
up: async ({ effects }) => {
// get old stats.yaml
diff --git a/startos/interfaces.ts b/startos/interfaces.ts
index 2e66a33..8cb2bc5 100644
--- a/startos/interfaces.ts
+++ b/startos/interfaces.ts
@@ -2,6 +2,7 @@ import { FileHelper } from '@start9labs/start-sdk'
import { sdk } from './sdk'
import { smpPort, webPort, xftpPort } from './utils'
import { smpServerIni } from './fileModels/smpServer.ini'
+import { i18n } from './i18n'
export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => {
// get fingerprints
@@ -35,9 +36,9 @@ export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => {
secure: { ssl: true },
})
const smp = sdk.createInterface(effects, {
- name: 'SMP Server',
+ name: i18n('SMP Server'),
id: 'smp',
- description: 'The SMP server for SimpleX',
+ description: i18n('The SMP server for SimpleX'),
type: 'api',
masked: true,
schemeOverride: { ssl: 'smp', noSsl: 'smp' },
@@ -56,9 +57,9 @@ export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => {
secure: { ssl: true },
})
const xftp = sdk.createInterface(effects, {
- name: 'XFTP Server',
+ name: i18n('XFTP Server'),
id: 'xftp',
- description: 'The XFTP server for SimpleX',
+ description: i18n('The XFTP server for SimpleX'),
type: 'api',
masked: true,
schemeOverride: { ssl: 'xftp', noSsl: 'xftp' },
diff --git a/startos/main.ts b/startos/main.ts
index 9f7b8cf..415af7a 100644
--- a/startos/main.ts
+++ b/startos/main.ts
@@ -2,6 +2,7 @@ import { fileServerIni } from './fileModels/fileServer.ini'
import { smpServerIni } from './fileModels/smpServer.ini'
import { sdk } from './sdk'
import { smpMounts, smpPort, xftpMounts, xftpPort } from './utils'
+import { i18n } from './i18n'
export const main = sdk.setupMain(async ({ effects }) => {
/**
@@ -9,16 +10,16 @@ export const main = sdk.setupMain(async ({ effects }) => {
*
* In this section, we fetch any resources or run any desired preliminary commands.
*/
- console.info('Starting SimpleX!')
+ console.info(i18n('Starting SimpleX!'))
// confirm configuration files are present and restart service if they change
const smpIni = await smpServerIni.read().const(effects)
if (!smpIni) {
- throw new Error('No smp-server.ini')
+ throw new Error(i18n('No smp-server.ini'))
}
const fileIni = await fileServerIni.read().const(effects)
if (!fileIni) {
- throw new Error('No file-server.ini')
+ throw new Error(i18n('No file-server.ini'))
}
/**
@@ -40,11 +41,11 @@ export const main = sdk.setupMain(async ({ effects }) => {
command: sdk.useEntrypoint(),
},
ready: {
- display: 'SMP Server',
+ display: i18n('SMP Server'),
fn: () =>
sdk.healthCheck.checkPortListening(effects, smpPort, {
- successMessage: 'The SMP server is ready',
- errorMessage: 'The SMP server is not ready',
+ successMessage: i18n('The SMP server is ready'),
+ errorMessage: i18n('The SMP server is not ready'),
}),
},
requires: [],
@@ -60,11 +61,11 @@ export const main = sdk.setupMain(async ({ effects }) => {
command: sdk.useEntrypoint(),
},
ready: {
- display: 'XFTP Server',
+ display: i18n('XFTP Server'),
fn: () =>
sdk.healthCheck.checkPortListening(effects, xftpPort, {
- successMessage: 'The XFTP server is ready',
- errorMessage: 'The XFTP server is not ready',
+ successMessage: i18n('The XFTP server is ready'),
+ errorMessage: i18n('The XFTP server is not ready'),
}),
},
requires: [],
diff --git a/startos/manifest/i18n.ts b/startos/manifest/i18n.ts
new file mode 100644
index 0000000..e91aa81
--- /dev/null
+++ b/startos/manifest/i18n.ts
@@ -0,0 +1,18 @@
+export default {
+ description: {
+ short: {
+ en_US: 'Freedom & security of your communications',
+ es_ES: 'Libertad y seguridad de tus comunicaciones',
+ de_DE: 'Freiheit und Sicherheit Ihrer Kommunikation',
+ pl_PL: 'Wolność i bezpieczeństwo Twojej komunikacji',
+ fr_FR: 'Liberté et sécurité de vos communications',
+ },
+ long: {
+ en_US: 'An open-source, decentralized messenger designed for maximum privacy and control.',
+ es_ES: 'Un mensajero descentralizado y de código abierto diseñado para máxima privacidad y control.',
+ de_DE: 'Ein quelloffener, dezentraler Messenger für maximale Privatsphäre und Kontrolle.',
+ pl_PL: 'Otwartoźródłowy, zdecentralizowany komunikator zaprojektowany z myślą o maksymalnej prywatności i kontroli.',
+ fr_FR: 'Un messager open source et décentralisé conçu pour une confidentialité et un contrôle maximaux.',
+ },
+ },
+}
diff --git a/startos/manifest.ts b/startos/manifest/index.ts
similarity index 76%
rename from startos/manifest.ts
rename to startos/manifest/index.ts
index 37fe397..ae2d799 100644
--- a/startos/manifest.ts
+++ b/startos/manifest/index.ts
@@ -1,4 +1,5 @@
import { setupManifest } from '@start9labs/start-sdk'
+import i18n from './i18n'
export const manifest = setupManifest({
id: 'simplex',
@@ -12,10 +13,7 @@ export const manifest = setupManifest({
'https://github.com/Start9Labs/simplex-startos/blob/update/040/docs/instructions.md',
donationUrl:
'https://github.com/simplex-chat/simplex-chat#help-us-with-donations',
- description: {
- short: 'Freedom & security of your communications',
- long: 'An open-source, decentralized messenger designed for maximum privacy and control.',
- },
+ description: i18n.description,
volumes: [
'smp-configs',
'smp-state',
@@ -30,13 +28,15 @@ export const manifest = setupManifest({
images: {
smp: {
source: {
- dockerTag: 'simplexchat/smp-server:v6.4.5',
+ dockerTag: 'simplexchat/smp-server:v7.0.0',
},
+ arch: ['x86_64', 'aarch64'],
},
xftp: {
source: {
- dockerTag: 'simplexchat/xftp-server:v6.4.5',
+ dockerTag: 'simplexchat/xftp-server:v7.0.0',
},
+ arch: ['x86_64', 'aarch64'],
},
},
dependencies: {},
From 4904955d102ac7a4f5d2b6e496eebff7469e5390 Mon Sep 17 00:00:00 2001
From: Matt Hill
Date: Sun, 1 Feb 2026 15:21:25 -0700
Subject: [PATCH 17/35] revert to 6.4.5
---
Makefile | 2 +-
startos/install/versions/index.ts | 2 +-
.../versions/{v7.0.0.0.b0.ts => v6.4.5.0.b0.ts} | 4 ++--
startos/manifest/index.ts | 10 ++++++----
4 files changed, 10 insertions(+), 8 deletions(-)
rename startos/install/versions/{v7.0.0.0.b0.ts => v6.4.5.0.b0.ts} (98%)
diff --git a/Makefile b/Makefile
index 10d4360..927e1fc 100644
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,3 @@
-ARCHES := x86 arm
+ARCHES := x86
# overrides to s9pk.mk must precede the include statement
include s9pk.mk
diff --git a/startos/install/versions/index.ts b/startos/install/versions/index.ts
index 7ae0e45..4ec2686 100644
--- a/startos/install/versions/index.ts
+++ b/startos/install/versions/index.ts
@@ -1,2 +1,2 @@
-export { v_7_0_0_0_b0 as current } from './v7.0.0.0.b0'
+export { v6_4_5_0_b0 as current } from './v6.4.5.0.b0'
export const other = []
diff --git a/startos/install/versions/v7.0.0.0.b0.ts b/startos/install/versions/v6.4.5.0.b0.ts
similarity index 98%
rename from startos/install/versions/v7.0.0.0.b0.ts
rename to startos/install/versions/v6.4.5.0.b0.ts
index 3f44526..e9c3b3a 100644
--- a/startos/install/versions/v7.0.0.0.b0.ts
+++ b/startos/install/versions/v6.4.5.0.b0.ts
@@ -8,8 +8,8 @@ import { join } from 'path'
// NOTE, adding passwords to xftp server addresses. Previous addresses are less secure and expected to break.
-export const v_7_0_0_0_b0 = VersionInfo.of({
- version: '7.0.0:0-beta.0',
+export const v6_4_5_0_b0 = VersionInfo.of({
+ version: '6.4.5:0-beta.0',
releaseNotes: {
en_US: 'Updated for StartOS 0.4.0',
es_ES: 'Actualizado para StartOS 0.4.0',
diff --git a/startos/manifest/index.ts b/startos/manifest/index.ts
index ae2d799..55315e7 100644
--- a/startos/manifest/index.ts
+++ b/startos/manifest/index.ts
@@ -28,15 +28,17 @@ export const manifest = setupManifest({
images: {
smp: {
source: {
- dockerTag: 'simplexchat/smp-server:v7.0.0',
+ dockerTag: 'simplexchat/smp-server:v6.4.5',
},
- arch: ['x86_64', 'aarch64'],
+ // @TODO aarch64 coming after 6.4.5
+ arch: ['x86_64'],
},
xftp: {
source: {
- dockerTag: 'simplexchat/xftp-server:v7.0.0',
+ dockerTag: 'simplexchat/xftp-server:v6.4.5',
},
- arch: ['x86_64', 'aarch64'],
+ // @TODO aarch64 coming after 6.4.5
+ arch: ['x86_64'],
},
},
dependencies: {},
From 959c9517ea3d06e3ff6538634c6b1b88e40ac621 Mon Sep 17 00:00:00 2001
From: Matt Hill
Date: Sat, 7 Feb 2026 13:47:17 -0700
Subject: [PATCH 18/35] Update README, CONTRIBUTING, and docsUrl
---
CONTRIBUTING.md | 15 +++
README.md | 192 ++++++++++++++++++++++++++++++++++++--
docs/README.md | 85 -----------------
startos/manifest/index.ts | 3 +-
4 files changed, 202 insertions(+), 93 deletions(-)
create mode 100644 CONTRIBUTING.md
delete mode 100644 docs/README.md
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/README.md b/README.md
index f54ca3d..0550671 100644
--- a/README.md
+++ b/README.md
@@ -2,14 +2,194 @@
-## Building from source
+# SimpleX Server for StartOS
-1. Set up your [environment](https://docs.start9.com/packaging-guide/environment-setup.html).
+This repository packages [SimpleX](https://github.com/simplex-chat/simplexmq) for StartOS. This document describes what makes this package different from a default SimpleX deployment.
-1. Clone this repository and `cd` into it.
+For general SimpleX usage and features, see the [upstream documentation](https://simplex.chat/docs/).
-1. run `make`.
+## How This Differs from Upstream
-1. The resulting `.s9pk` can be side loaded into StartOS.
+This package runs both SMP (messaging) and XFTP (file transfer) servers with auto-generated credentials and pre-configured defaults. Connection URLs with embedded fingerprints and passwords are automatically generated for easy client configuration.
-For a complete list of build options, see the [docs](https://docs.start9.com/packaging-guide/building.html)
+## Container Runtime
+
+This package runs **2 containers**:
+
+| Container | Image | Purpose |
+|-----------|-------|---------|
+| smp | `simplexchat/smp-server:v6.4.5` | SimpleX Messaging Protocol server |
+| xftp | `simplexchat/xftp-server:v6.4.5` | SimpleX File Transfer Protocol server |
+
+**Note:** Currently x86_64 only. ARM64 support coming after v6.4.5.
+
+## Volumes
+
+| Volume | Contents | Backed Up |
+|--------|----------|-----------|
+| `smp-configs` | SMP server configuration, keys, fingerprint | Yes |
+| `smp-state` | SMP message queues and state | Yes |
+| `xftp-configs` | XFTP server configuration, keys, fingerprint | Yes |
+| `xftp-state` | XFTP file metadata and state | Yes |
+| `xftp-files` | Uploaded file storage | Yes |
+
+## Install Flow
+
+On installation:
+1. Generates shared authentication password
+2. Initializes SMP server (`smp-server init`) - creates keys and fingerprint
+3. Initializes XFTP server (`xftp-server init`) - creates keys and fingerprint
+4. Writes configuration files with defaults
+
+## Configuration Management
+
+### Auto-Configured Settings
+
+**SMP Server:**
+
+| Setting | Value | Purpose |
+|---------|-------|---------|
+| Port | 5223 (and 443) | Client connections |
+| Control port | 5224 | Server management |
+| Message expiry | 365 days | Auto-delete old messages |
+| Notification expiry | 168 hours (7 days) | Push notification cleanup |
+| Store queues | Memory | Performance optimization |
+| Password | Auto-generated | Queue creation auth |
+
+**XFTP Server:**
+
+| Setting | Value | Purpose |
+|---------|-------|---------|
+| Port | 5225 | File transfers |
+| Control port | 5226 | Server management |
+| Storage quota | 10 GB | Maximum file storage |
+| File expiry | 168 hours (7 days) | Auto-delete uploaded files |
+| Password | Shared with SMP | File upload auth |
+
+### User-Configurable Settings
+
+No configuration actions are provided. Advanced users can modify the INI files directly in the config volumes.
+
+## Network Interfaces
+
+| Interface | Type | Port | Scheme | Description |
+|-----------|------|------|--------|-------------|
+| SMP Server | api | 5223 | `smp://` | Messaging protocol |
+| XFTP Server | api | 5225 | `xftp://` | File transfer protocol |
+
+Both interfaces are masked and include credentials in the connection URL format:
+```
+smp://:@:5223
+xftp://:@:5225
+```
+
+## Actions
+
+None. Server configuration is automatic.
+
+## Dependencies
+
+None. SimpleX servers are standalone.
+
+## Backups
+
+All server data is backed up:
+- `smp-configs` - server identity and settings
+- `smp-state` - message queues
+- `xftp-configs` - server identity and settings
+- `xftp-state` - file metadata
+- `xftp-files` - uploaded files
+
+**Important:** The server fingerprint is part of your identity. Losing the config volumes means clients must reconnect to a "new" server.
+
+## Health Checks
+
+| Check | Method | Success Condition |
+|-------|--------|-------------------|
+| SMP Server | Port listening | Port 5223 responds |
+| XFTP Server | Port listening | Port 5225 responds |
+
+## Using with SimpleX Chat
+
+1. Copy the SMP server URL from StartOS interfaces
+2. In SimpleX Chat app: Settings → Network & servers → SMP servers
+3. Add your server URL
+4. Repeat for XFTP server URL under "XFTP servers"
+
+Your messages and files will now route through your own servers.
+
+## Limitations
+
+1. **x86_64 only**: ARM64 (aarch64) not yet supported in this version
+2. **No web UI**: Server administration is config-file only
+3. **Fixed storage quota**: XFTP limited to 10GB (requires config edit to change)
+4. **No stats dashboard**: Prometheus metrics available but not exposed
+
+## What's Unchanged
+
+- Full SMP/XFTP protocol support
+- End-to-end encryption
+- Message queue functionality
+- File transfer capabilities
+- Client compatibility
+
+---
+
+## Quick Reference (YAML)
+
+```yaml
+package_id: simplex
+upstream_version: 6.4.5
+arch: x86_64 only
+
+containers:
+ - name: smp
+ image: simplexchat/smp-server:v6.4.5
+ ports: [5223, 5224]
+ - name: xftp
+ image: simplexchat/xftp-server:v6.4.5
+ ports: [5225, 5226]
+
+volumes:
+ smp-configs:
+ backup: true
+ smp-state:
+ backup: true
+ xftp-configs:
+ backup: true
+ xftp-state:
+ backup: true
+ xftp-files:
+ backup: true
+
+interfaces:
+ smp:
+ type: api
+ port: 5223
+ scheme: smp://
+ includes: fingerprint, password
+ xftp:
+ type: api
+ port: 5225
+ scheme: xftp://
+ includes: fingerprint, password
+
+actions: []
+
+dependencies: []
+
+auto_configure:
+ - server fingerprints (generated)
+ - shared password (generated)
+ - message expiry: 365 days
+ - file expiry: 7 days
+ - storage quota: 10GB
+
+health_checks:
+ - name: SMP Server
+ method: port_listening
+ port: 5223
+ - name: XFTP Server
+ method: port_listening
+ port: 5225
+```
diff --git a/docs/README.md b/docs/README.md
deleted file mode 100644
index 828a0ca..0000000
--- a/docs/README.md
+++ /dev/null
@@ -1,85 +0,0 @@
-# Getting started with SimpleX
-
-## About
-
-Your SimpleX Server comes with a [SMP server](https://simplex.chat/docs/server.html) for sending/receiving messages and an [XFTP server](https://simplex.chat/docs/xftp-server.html) for transferring files and media.
-
-In SimpleX, participants choose which servers to use for receiving messages and files. I receive through my private server, and you receive through your private server. Or we could both receive through the same private server. Or we could each use different public servers.
-
-SimpleX servers function as queues. Servers only store messages until a client retrieves them, after which they are deleted. This means message histories are stored on client devices, so it is client devices that must be backed up to prevent loss of messages.
-
-For detailed instructions on using SimpleX, check out the [official docs](https://simplex.chat/docs/guide/readme.html).
-
-## Initial setup
-
-1. Ensure your SimpleX Server is running, and health checks are passing.
-
-1. Follow instructions in the [Start9 docs](https://docs.start9.com) for running Tor on your client device (phone/laptop).
-
-1. Download and install the [SimpleX app](https://simplex.chat/) on your client device.
-
-1. During initial setup of the app, if you choose to create a profile, _do not_ create a SimpleX address. You can do this later, once you have configured your servers, since your server is included in your address.
-
-## Creating a fully anonymous profile
-
-The purpose of this profile is _total_ anonymity. For this profile, you will receive messages through your self-hosted server over Tor, and you only permit sending messages to other .onion servers.
-
-For sending messages to non-Tor servers, we recommend creating a separate profile entirely.
-
-SimpleX can be configured many different ways, depending on your threat model and privacy goals, each with different tradeoffs.
-
-### Enable and enforce Tor
-
-1. If you did not create a profile during initial setup, create one now.
-
-1. Navigate to `Settings > Network & servers`.
-
-1. For iOS users: Skip this step. For other platforms: Enable "Use SOCKS proxy".
-
-1. Set `Use .onion hosts` to "Required" (this might be under `SOCKS proxy settings` or `Advanced network settings`). This means that this profile can _only_ send messages to other .onion servers.
-
-1. Finally, in `Advanced Network Settings`, set `Private Routing` to "Never".
-
-### Connect your SMP server for messages
-
-1. Navigate to `Settings > Network & servers > Message servers`.
-
-1. You will see some default receiving servers (e.g. smp1, smp2, smp3). It is recommended you _delete them all_ to ensure you do not accidentally use them in the future. If you choose not to delete them, at least disable the "Use for new connections" setting on each of them. This will prevent them from being used without your explicit instruction.
-
-1. Click "Add Server".
-
-1. Tap "Scan QR Code".
-
-1. Scan your SMP Server Address QR code, located in StartOS UI under `SimpleX Server > Properties > SimpleX SMP Server Address`.
-
-1. Click on the newly added server.
-
-1. Click "Test server" and wait for the test to pass.
-
-1. Enable "Use for new connections".
-
-1. Navigate back and click "Save servers".
-
-### Connect your XFTP server for media and files
-
-1. Navigate to `Settings > Network & servers > Media and file servers`.
-
-1. You will see some default receiving servers (e.g. xftp1, xftp2, xftp3). It is recommended you _delete them all_ to ensure you do not accidentally use them in the future. If you choose not to delete them, at least disable the "Use for new connections" setting on each of them. This will prevent them from being used without your explicit instruction.
-
-1. Click "Add server".
-
-1. Tap "Scan QR Code".
-
-1. Scan your XFTP Server Address QR code, located in StartOS UI under `SimpleX Server > Properties > SimpleX XFTP Server Address`.
-
-1. Click on the newly added server.
-
-1. Click "Test server" and wait for the test to pass.
-
-1. Enable "Use for new connections".
-
-1. Navigate back and click "Save servers".
-
-### Create a SimpleX address (optional)
-
-You can now create a SimpleX Address if you want, but you should _not_ share the address publicly, as it will link the .onion URL to your identity. Remember, the purpose of this profile is total anonymity.
diff --git a/startos/manifest/index.ts b/startos/manifest/index.ts
index 55315e7..404ef35 100644
--- a/startos/manifest/index.ts
+++ b/startos/manifest/index.ts
@@ -9,8 +9,7 @@ export const manifest = setupManifest({
upstreamRepo: 'https://github.com/simplex-chat/simplexmq/',
supportSite: 'https://github.com/simplex-chat/simplexmq//issues',
marketingSite: 'https://simplex.chat/',
- docsUrl:
- 'https://github.com/Start9Labs/simplex-startos/blob/update/040/docs/instructions.md',
+ docsUrl: 'https://simplex.chat/docs/',
donationUrl:
'https://github.com/simplex-chat/simplex-chat#help-us-with-donations',
description: i18n.description,
From 3986a909c1d6bfaa72819e747a5b02db02c0a385 Mon Sep 17 00:00:00 2001
From: Matt Hill
Date: Sat, 7 Feb 2026 15:01:47 -0700
Subject: [PATCH 19/35] Add S3 upload support to release workflow
---
.github/workflows/releaseService.yml | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/releaseService.yml b/.github/workflows/releaseService.yml
index ecc6a66..17a0b91 100644
--- a/.github/workflows/releaseService.yml
+++ b/.github/workflows/releaseService.yml
@@ -10,8 +10,11 @@ jobs:
uses: start9labs/shared-workflows/.github/workflows/releaseService.yml@master
with:
# FREE_DISK_SPACE: true
- 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
From da4e65373d3c394d8a714866f27360b8634246b4 Mon Sep 17 00:00:00 2001
From: Matt Hill
Date: Sat, 7 Feb 2026 17:01:17 -0700
Subject: [PATCH 20/35] Update s9pk.mk
---
s9pk.mk | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
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)
From 2cb94d6cdae1a9c4a6e94d3611f346342e841ee0 Mon Sep 17 00:00:00 2001
From: Matt Hill
Date: Wed, 4 Mar 2026 09:44:06 -0700
Subject: [PATCH 21/35] beta 55
---
.github/workflows/buildService.yml | 4 +-
README.md | 2 +-
icon.png | Bin 34268 -> 0 bytes
icon.svg | 11 ++
package-lock.json | 83 ++++----
package.json | 2 +-
startos/fileModels/fileServer.ini.ts | 108 +++++-----
startos/fileModels/smpServer.ini.ts | 187 +++++++-----------
startos/install/versionGraph.ts | 1 -
startos/install/versions/index.ts | 2 +-
.../{v6.4.5.0.b0.ts => v6.4.5.1.b0.ts} | 14 +-
startos/main.ts | 2 +-
startos/manifest/index.ts | 7 +-
13 files changed, 197 insertions(+), 226 deletions(-)
delete mode 100644 icon.png
create mode 100644 icon.svg
rename startos/install/versions/{v6.4.5.0.b0.ts => v6.4.5.1.b0.ts} (92%)
diff --git a/.github/workflows/buildService.yml b/.github/workflows/buildService.yml
index feb2ae9..00ab89b 100644
--- a/.github/workflows/buildService.yml
+++ b/.github/workflows/buildService.yml
@@ -4,10 +4,10 @@ on:
workflow_dispatch:
pull_request:
paths-ignore: ['*.md']
- branches: ['main', 'master', 'update/040']
+ branches: ['master']
push:
paths-ignore: ['*.md']
- branches: ['main', 'master', 'update/040']
+ branches: ['master']
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
diff --git a/README.md b/README.md
index 0550671..73c5e35 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@