Initial StartOS 0.3.5.1 packaging scaffold for Kamado Pool

Multi-stage Dockerfile clones KamadoPool at a pinned SHA, builds
ckpool and kamado-api (with embedded Svelte UI), runtime image
supervises both processes via tini + wait -n. Config covers
bitcoind mainnet/testnet4 variant, payout address, coinbase tag,
vardiff knobs, and log level. Web UI interface only — stratum
:3333 requires a router port-forward or simpleproxy workaround
because StartOS 0.3.x does not forward raw TCP on LAN.

TODO before first build: pin KAMADO_REPO + KAMADO_SHA in the
Dockerfile to a pushed commit.
This commit is contained in:
satoshi
2026-04-13 03:51:23 +03:00
commit 4064f9e56a
18 changed files with 689 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
import { bundle } from "https://deno.land/x/emit@0.40.0/mod.ts";
const result = await bundle(new URL("./embassy.ts", import.meta.url));
await Deno.writeTextFile("scripts/embassy.js", result.code);
+1
View File
@@ -0,0 +1 @@
export * from "https://deno.land/x/embassyd_sdk@v0.3.3.0.11/mod.ts";
+6
View File
@@ -0,0 +1,6 @@
export { getConfig } from "./procedures/getConfig.ts";
export { setConfig } from "./procedures/setConfig.ts";
export { dependencies } from "./procedures/dependencies.ts";
export { health } from "./procedures/healthChecks.ts";
export { migration } from "./procedures/migrations.ts";
export { properties } from "./procedures/properties.ts";
+45
View File
@@ -0,0 +1,45 @@
import { matches, types as T } from "../deps.ts";
const { shape, boolean, arrayOf, string, dictionary, any } = matches;
const bitcoindMatcher = shape({
"rpc": shape({
"enable": boolean,
"username": string,
"password": string,
}, ["enable", "username", "password"]),
"advanced": shape({
"peers": shape({
"listen": boolean,
}, ["listen"]),
}, ["peers"]),
}, ["rpc", "advanced"]);
const checkBitcoind: T.ExpectedExports.dependencyConfig["check"] = async (
effects,
input,
) => {
const res = bitcoindMatcher.test(input);
if (!res) return { error: "Bitcoin Core RPC settings are missing." };
if (!input.rpc.enable) {
return { error: "Bitcoin Core RPC must be enabled." };
}
return { result: null };
};
const autoBitcoind: T.ExpectedExports.dependencyConfig["autoConfigure"] =
async (effects, input) => {
input.rpc.enable = true;
return { result: input };
};
export const dependencies: T.ExpectedExports.dependencies = {
"bitcoind": {
check: checkBitcoind,
autoConfigure: autoBitcoind,
},
"bitcoind-testnet": {
check: checkBitcoind,
autoConfigure: autoBitcoind,
},
};
+135
View File
@@ -0,0 +1,135 @@
import { compat, types as T } from "../deps.ts";
export const getConfig: T.ExpectedExports.getConfig = compat.getConfig({
"bitcoind": {
"type": "union",
"name": "Bitcoin Core",
"description": "Select which Bitcoin Core node Kamado should use.",
"tag": {
"id": "type",
"name": "Type",
"description": "Mainnet bitcoind or the testnet4 variant.",
"variant-names": {
"bitcoind": "Bitcoin Core (mainnet)",
"bitcoind-testnet": "Bitcoin Core (testnet4)",
},
},
"default": "bitcoind",
"variants": {
"bitcoind": {
"user": {
"type": "pointer",
"name": "RPC Username",
"description": "The RPC username from Bitcoin Core.",
"subtype": "package",
"package-id": "bitcoind",
"target": "config",
"multi": false,
"selector": "$.rpc.username",
},
"password": {
"type": "pointer",
"name": "RPC Password",
"description": "The RPC password from Bitcoin Core.",
"subtype": "package",
"package-id": "bitcoind",
"target": "config",
"multi": false,
"selector": "$.rpc.password",
},
},
"bitcoind-testnet": {
"user": {
"type": "pointer",
"name": "RPC Username",
"description": "The RPC username from Bitcoin Core (testnet4).",
"subtype": "package",
"package-id": "bitcoind-testnet",
"target": "config",
"multi": false,
"selector": "$.rpc.username",
},
"password": {
"type": "pointer",
"name": "RPC Password",
"description": "The RPC password from Bitcoin Core (testnet4).",
"subtype": "package",
"package-id": "bitcoind-testnet",
"target": "config",
"multi": false,
"selector": "$.rpc.password",
},
},
},
},
"pool-address": {
"type": "string",
"name": "Payout Address",
"description":
"Bitcoin address that receives the full block reward when a block is solved. Solo mining — no fees, no splits.",
"nullable": false,
"masked": false,
"copyable": true,
},
"pool-identifier": {
"type": "string",
"name": "Coinbase Tag",
"description":
"Short string embedded in the coinbase transaction of solved blocks. Max 32 characters.",
"nullable": false,
"default": "/Kamado/",
"masked": false,
"copyable": false,
},
"startdiff": {
"type": "number",
"name": "Starting Difficulty",
"description":
"Initial vardiff target for new miner connections. Bitaxe-class miners typically land around 16384.",
"nullable": false,
"default": 16384,
"range": "[1,*)",
"integral": true,
},
"mindiff": {
"type": "number",
"name": "Minimum Difficulty",
"description": "Floor for the vardiff algorithm.",
"nullable": false,
"default": 1000,
"range": "[1,*)",
"integral": true,
},
"maxdiff": {
"type": "number",
"name": "Maximum Difficulty",
"description": "Ceiling for the vardiff algorithm. 0 means no cap.",
"nullable": false,
"default": 0,
"range": "[0,*)",
"integral": true,
},
"dropidle": {
"type": "number",
"name": "Drop Idle (seconds)",
"description":
"Disconnect clients that have not submitted a share in this many seconds. 0 disables the idle disconnect.",
"nullable": false,
"default": 0,
"range": "[0,*)",
"integral": true,
},
"log-level": {
"type": "enum",
"name": "Log Level",
"description": "Verbosity of the kamado-api log output.",
"values": ["debug", "info", "warn", "error"],
"value-names": {
"debug": "Debug",
"info": "Info",
"warn": "Warn",
"error": "Error",
},
"default": "info",
},
});
+10
View File
@@ -0,0 +1,10 @@
import { healthUtil, types as T } from "../deps.ts";
export const health: T.ExpectedExports.health = {
"web": async (effects, duration) => {
return healthUtil.checkWebUrl("http://kamado-pool.embassy:8080/api/health")(
effects,
duration,
);
},
};
+4
View File
@@ -0,0 +1,4 @@
import { compat, types as T } from "../deps.ts";
export const migration: T.ExpectedExports.migration = compat.migrations
.fromMapping({}, "0.1.0");
+22
View File
@@ -0,0 +1,22 @@
import { types as T, YAML } from "../deps.ts";
const noProps: T.ExpectedExports.properties = async () => {
return {
result: {
version: 2,
data: {
"Dashboard": {
type: "string",
value:
"Open the Kamado web UI from the Services page for live stats.",
description: "Kamado exposes everything through the web dashboard.",
copyable: false,
qr: false,
masked: false,
},
},
},
};
};
export const properties = noProps;
+14
View File
@@ -0,0 +1,14 @@
import { compat, types as T } from "../deps.ts";
export const setConfig: T.ExpectedExports.setConfig = compat.setConfig({
"bitcoind": {
"bitcoind": [{
"name": "bitcoind",
"version": ">=24.0.0 <30.0.0",
}],
"bitcoind-testnet": [{
"name": "bitcoind-testnet",
"version": ">=24.0.0 <30.0.0",
}],
},
});