8.5 KiB
8.5 KiB
Packaging Apps for Neode/StartOS
This guide explains how to package containerized applications (like nostrdevs/atob) as .s9pk files for installation on Neode.
What is an S9PK?
An .s9pk file is a package format for Neode/StartOS that contains:
- Manifest (metadata, dependencies, interfaces)
- Docker Images (your containerized app)
- Icon (PNG/WEBP/JPG)
- License (LICENSE.md)
- Instructions (INSTRUCTIONS.md)
- Configuration (optional config.yaml)
- Actions (optional scripts for user actions)
Prerequisites
-
Install StartOS SDK (needed for packing):
# Clone the Neode repo (you already have this) cd /Users/tx1138/Code/Neode # Build the SDK cd core cargo build --release --bin startos # The binary will be at: target/release/startos -
Docker for building container images
Creating an S9PK for nostrdevs/atob
Step 1: Create Package Directory Structure
mkdir -p ~/atob-package
cd ~/atob-package
Create the following structure:
atob-package/
├── manifest.yaml # Package metadata
├── LICENSE.md # License file
├── INSTRUCTIONS.md # User instructions
├── icon.png # 512x512 icon
├── docker_images/ # Docker image archive
│ └── aarch64.tar # or x86_64.tar
└── scripts/
└── procedures/
└── main.ts # Main entry point
Step 2: Create manifest.yaml
id: atob
title: "ATOB"
version: "0.1.0"
release-notes: "Initial release"
license: MIT
wrapper-repo: "https://github.com/nostrdevs/atob"
upstream-repo: "https://github.com/nostrdevs/atob"
support-site: "https://github.com/nostrdevs/atob/issues"
marketing-site: "https://github.com/nostrdevs/atob"
donation-url: null
description:
short: "ATOB - A containerized application for Nostr"
long: |
ATOB is a containerized application designed for the Nostr ecosystem.
This package runs ATOB on your Neode server with automatic configuration.
# Assets
assets:
license: LICENSE.md
icon: icon.png
instructions: INSTRUCTIONS.md
docker-images: docker_images
# Main container
main:
type: docker
image: main
entrypoint: "docker_entrypoint.sh"
args: []
mounts:
main: /data
# Volumes
volumes:
main:
type: data
# Interfaces (exposed services)
interfaces:
main:
name: Web Interface
description: Main ATOB web interface
tor-config:
port-mapping:
80: "80"
lan-config:
443:
ssl: true
internal: 80
ui: true
protocols:
- tcp
- http
# Health checks
health-checks:
web-ui:
name: Web Interface
success-message: "ATOB is ready!"
type: docker
image: main
entrypoint: "check-web.sh"
args: []
io-format: yaml
inject: true
# Configuration (optional)
config: ~
# Properties
properties: ~
# Dependencies
dependencies: {}
# Backup configuration
backup:
create:
type: docker
image: compat
system: true
entrypoint: compat
args:
- duplicity
- create
- /mnt/backup
- /data
mounts:
BACKUP: /mnt/backup
main: /data
restore:
type: docker
image: compat
system: true
entrypoint: compat
args:
- duplicity
- restore
- /mnt/backup
- /data
mounts:
BACKUP: /mnt/backup
main: /data
# Migrations (for updates)
migrations:
from:
"*":
type: none
to:
"*":
type: none
Step 3: Create LICENSE.md
Copy your project's license or create a simple one:
# MIT License
Copyright (c) 2025 Nostr Devs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction...
Step 4: Create INSTRUCTIONS.md
# ATOB Instructions
## Getting Started
1. After installation, ATOB will be available at the interface URL
2. Access it through the Neode dashboard
3. Configuration is automatic
## Usage
[Add specific instructions for your app]
## Support
For issues, visit: https://github.com/nostrdevs/atob/issues
Step 5: Add an Icon
Create or download a 512x512 PNG icon and save it as icon.png
Step 6: Export Docker Image
# Build your Docker image
cd /path/to/atob
docker build -t atob:latest .
# Save the image
mkdir -p ~/atob-package/docker_images
docker save atob:latest -o ~/atob-package/docker_images/$(uname -m).tar
# The filename should match your architecture:
# - x86_64.tar for Intel/AMD
# - aarch64.tar for ARM64/Apple Silicon
Step 7: Create scripts/procedures/main.ts
This is the entry point for your service:
import { types as T, matches, YAML } from "../deps.ts";
// This is the main entry point for your service
export const main: T.ExpectedExports.main = async (effects: T.Effects) => {
return await effects.createContainer({
image: "main",
entrypoint: ["/bin/sh"],
mounts: {
main: "/data",
},
});
};
// Properties that will be displayed in the UI
export const properties: T.ExpectedExports.properties = async (
effects: T.Effects
) => {
return {
version: "0.1.0",
"Automatic TOR Address": {
type: "string",
value: effects.interfaces.main.torAddress,
qr: true,
copyable: true,
masked: false,
},
};
};
// Health check
export const health: T.ExpectedExports.health = async (effects: T.Effects) => {
return await effects.health.checkWebUrl("http://main.embassy:80");
};
Step 8: Build the S9PK
# Navigate to your package directory
cd ~/atob-package
# Use the StartOS CLI to pack it
/Users/tx1138/Code/Neode/core/target/release/startos pack
# This will create: atob.s9pk
Step 9: Install on Neode
Option A: Via CLI (Direct)
# Copy the .s9pk to your Neode server
scp atob.s9pk user@neode-server:/tmp/
# SSH into the server
ssh user@neode-server
# Install using CLI
startos package.sideload /tmp/atob.s9pk
Option B: Via UI (Once Marketplace is Connected)
- Navigate to Marketplace in Neode UI
- Click "Sideload Package"
- Upload
atob.s9pk - Wait for installation to complete
Testing Your Package
Validate Before Installing
# Inspect the package without installing
/Users/tx1138/Code/Neode/core/target/release/startos inspect atob.s9pk
Development Workflow
- Make changes to your manifest or scripts
- Rebuild the s9pk:
startos pack - Uninstall old version:
startos package.uninstall atob - Install new version:
startos package.sideload atob.s9pk
Advanced Features
Adding Configuration Options
Add to manifest.yaml:
config:
get:
type: script
set:
type: script
# Then create scripts/procedures/getConfig.ts and setConfig.ts
Adding User Actions
actions:
restart-service:
name: "Restart Service"
description: "Manually restart the ATOB service"
warning: "This will temporarily interrupt service"
allowed-statuses:
- running
implementation:
type: docker
image: main
entrypoint: "restart.sh"
Multi-Architecture Support
Build for multiple architectures:
# Build for x86_64
docker buildx build --platform linux/amd64 -t atob:amd64 .
docker save atob:amd64 -o docker_images/x86_64.tar
# Build for ARM64
docker buildx build --platform linux/arm64 -t atob:arm64 .
docker save atob:arm64 -o docker_images/aarch64.tar
Resources
- StartOS Package Manifest Schema: Official Docs
- Example Packages:
/Users/tx1138/Code/Neode/core/startos/test/ - SDK Reference: Built binaries in
core/target/release/
Troubleshooting
Package Won't Install
- Check manifest syntax:
yamllint manifest.yaml - Verify docker image exists:
tar -tzf docker_images/aarch64.tar | head - Check logs on server:
journalctl -u startos -f
Service Won't Start
- Check container logs:
docker logs $(docker ps -a | grep atob | awk '{print $1}') - Verify entrypoint script exists and is executable
- Check volume mounts in manifest
Interface Not Accessible
- Verify port mappings in
interfacessection - Check that your container is listening on the correct port
- Wait for TOR address generation (can take 2-3 minutes)
Quick Reference
# Pack a package
startos pack
# Inspect a package
startos inspect atob.s9pk
# Install (CLI)
startos package.sideload atob.s9pk
# List installed packages
startos package.list
# Uninstall
startos package.uninstall atob
# Check package status
startos package.properties atob