feat(secubox): complete meta-script generator Tasks 14-17

Task 14: Arch Profiles
- Add profiles/arch/arm64.yaml and profiles/arch/amd64.yaml
- Add ResolveWithArch() to merger for base → arch → tier chain
- Add tests for arch profile resolution

Task 15: Package secubox.yaml for All Packages
- Add scripts/generate-secubox-yaml.py generator script
- Generate 131 debian/secubox.yaml files with:
  - Category detection (security, network, system, etc.)
  - Tier assignment (all, lite, standard, pro)
  - API socket and UI path detection

Task 16: APT Repository Setup
- Add apt/conf/ reprepro configuration (multi-arch arm64/amd64)
- Add apt/hooks/lintian-check pre-publish validation
- Add scripts/apt-publish.sh and scripts/apt-sync.sh
- Add apt/README.md documentation

Task 17: CI Integration
- Add .github/workflows/build-secubox-cli.yml
- Build for linux-amd64 and linux-arm64
- Version injection via ldflags
- GitHub releases on tag push

Code Quality Fixes (Tasks 10-13):
- Add atomic writes for OTA boot control files (0600 perms)
- Fix NVME device extraction (nvme0n1p2 → nvme0n1)
- Add scanner.Err() checks in hardware detection
- Fix URL injection validation in fetch command
- Add proper cleanup on checksum failures

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-05-11 05:32:29 +02:00
parent 295a91aac4
commit bd7dda0c6f
154 changed files with 3880 additions and 111 deletions

201
.github/workflows/build-secubox-cli.yml vendored Normal file
View File

@ -0,0 +1,201 @@
# SecuBox CLI — Build Go Binary
# Builds the secubox meta-script generator for linux-amd64 and linux-arm64
name: Build SecuBox CLI
on:
push:
branches: [master, main]
paths:
- 'cmd/secubox/**'
- '.github/workflows/build-secubox-cli.yml'
tags: ['v*']
pull_request:
branches: [master, main]
paths:
- 'cmd/secubox/**'
workflow_dispatch:
inputs:
version:
description: 'Version override (e.g., v2.8.0)'
required: false
env:
GO_VERSION: '1.22'
jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache-dependency-path: cmd/secubox/go.sum
- name: Download dependencies
working-directory: cmd/secubox
run: go mod download
- name: Run tests
working-directory: cmd/secubox
run: go test -v -race -coverprofile=coverage.out ./...
- name: Upload coverage
uses: actions/upload-artifact@v4
with:
name: coverage
path: cmd/secubox/coverage.out
build:
name: Build
needs: test
runs-on: ubuntu-latest
strategy:
matrix:
include:
- goos: linux
goarch: amd64
suffix: linux-amd64
- goos: linux
goarch: arm64
suffix: linux-arm64
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # For git describe
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache-dependency-path: cmd/secubox/go.sum
- name: Determine version
id: version
run: |
if [ -n "${{ github.event.inputs.version }}" ]; then
VERSION="${{ github.event.inputs.version }}"
elif [[ "${{ github.ref }}" == refs/tags/* ]]; then
VERSION="${{ github.ref_name }}"
else
VERSION="$(git describe --tags --always --dirty 2>/dev/null || echo 'dev')"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Building version: $VERSION"
- name: Build binary
working-directory: cmd/secubox
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: 0
run: |
VERSION="${{ steps.version.outputs.version }}"
BUILD_TIME=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
COMMIT=$(git rev-parse --short HEAD)
go build -ldflags="-s -w \
-X main.Version=$VERSION \
-X main.BuildTime=$BUILD_TIME \
-X main.Commit=$COMMIT" \
-o secubox-${{ matrix.suffix }} .
- name: Compress binary
working-directory: cmd/secubox
run: |
tar -czvf secubox-${{ matrix.suffix }}.tar.gz secubox-${{ matrix.suffix }}
sha256sum secubox-${{ matrix.suffix }}.tar.gz > secubox-${{ matrix.suffix }}.tar.gz.sha256
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: secubox-${{ matrix.suffix }}
path: |
cmd/secubox/secubox-${{ matrix.suffix }}.tar.gz
cmd/secubox/secubox-${{ matrix.suffix }}.tar.gz.sha256
release:
name: Release
needs: build
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts/
merge-multiple: true
- name: Generate release notes
run: |
cat > RELEASE_NOTES.md << 'EOF'
# SecuBox CLI ${{ github.ref_name }}
Meta-script generator for SecuBox-DEB image building.
## Features
- Profile-based image generation
- Board detection and auto-configuration
- A/B partition OTA updates
- GitHub releases integration
## Downloads
| Platform | File | SHA256 |
|----------|------|--------|
| Linux x64 | `secubox-linux-amd64.tar.gz` | [checksum] |
| Linux ARM64 | `secubox-linux-arm64.tar.gz` | [checksum] |
## Installation
```bash
# Linux x64
curl -LO https://github.com/CyberMind-FR/secubox-deb/releases/download/${{ github.ref_name }}/secubox-linux-amd64.tar.gz
tar xzf secubox-linux-amd64.tar.gz
sudo mv secubox-linux-amd64 /usr/local/bin/secubox
# Linux ARM64
curl -LO https://github.com/CyberMind-FR/secubox-deb/releases/download/${{ github.ref_name }}/secubox-linux-arm64.tar.gz
tar xzf secubox-linux-arm64.tar.gz
sudo mv secubox-linux-arm64 /usr/local/bin/secubox
```
## Quick Start
```bash
# Interactive wizard
secubox gen
# Generate for specific board
secubox gen --board mochabin --tier pro
# Build image
secubox build --board mochabin --output secubox.img
# Check hardware
secubox info
# OTA update
secubox ota update --url https://releases.secubox.in/v2.8.0/secubox-mochabin.img.gz
```
EOF
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
name: SecuBox CLI ${{ github.ref_name }}
body_path: RELEASE_NOTES.md
files: |
artifacts/*.tar.gz
artifacts/*.sha256
draft: false
prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') || contains(github.ref, 'rc') }}

137
apt/README.md Normal file
View File

@ -0,0 +1,137 @@
# SecuBox-DEB APT Repository
Configuration files for the SecuBox APT package repository.
## Repository Structure
```
/srv/apt/
├── conf/
│ ├── distributions # Repository distributions config
│ ├── options # Global reprepro options
│ └── incoming # Incoming packages config
├── db/ # Reprepro database
├── dists/ # Distribution metadata
│ ├── bookworm/ # Stable releases
│ └── bookworm-testing/ # Testing releases
├── pool/ # Package files
│ ├── main/ # Main component
│ └── contrib/ # Contrib component
├── incoming/ # Incoming packages drop
└── tmp/ # Temporary files
```
## Setup
### Initialize Repository
```bash
# Create directory structure
sudo mkdir -p /srv/apt/{conf,db,dists,pool,incoming,tmp}
# Copy configuration
sudo cp apt/conf/* /srv/apt/conf/
# Set ownership
sudo chown -R $(whoami) /srv/apt
# Initialize
cd /srv/apt
reprepro export
```
### GPG Key Setup
```bash
# Generate signing key (if not exists)
gpg --gen-key
# Export public key for clients
gpg --armor --export your@email.com > /srv/apt/secubox.gpg
```
## Usage
### Publishing Packages
```bash
# Publish a single package
./scripts/apt-publish.sh packages/secubox-core/*.deb
# Publish to testing
./scripts/apt-publish.sh -c bookworm-testing packages/*/*.deb
# Skip lintian (not recommended)
./scripts/apt-publish.sh --skip-lintian packages/*/*.deb
# Dry run
./scripts/apt-publish.sh -n packages/*/*.deb
```
### Syncing to Remote
```bash
# Preview sync
./scripts/apt-sync.sh --dry-run
# Full sync
./scripts/apt-sync.sh
# Verbose sync
./scripts/apt-sync.sh --verbose
```
### Manual reprepro Commands
```bash
cd /srv/apt
# Add package
reprepro -C main includedeb bookworm /path/to/package.deb
# Remove package
reprepro remove bookworm package-name
# List packages
reprepro list bookworm
# Check repository
reprepro check
```
## Client Configuration
### Adding Repository
```bash
# Add GPG key
curl -fsSL https://apt.secubox.in/secubox.gpg | sudo gpg --dearmor -o /usr/share/keyrings/secubox.gpg
# Add repository
echo "deb [signed-by=/usr/share/keyrings/secubox.gpg] https://apt.secubox.in bookworm main" | \
sudo tee /etc/apt/sources.list.d/secubox.list
# Update and install
sudo apt update
sudo apt install secubox-core
```
## Distributions
| Codename | Description | Use Case |
|----------|-------------|----------|
| bookworm | Stable releases | Production |
| bookworm-testing | Testing releases | Pre-release testing |
## Components
| Component | Description |
|-----------|-------------|
| main | Core SecuBox packages |
| contrib | Community contributions |
## Architectures
- `arm64` - Primary target (MOCHAbin, ESPRESSObin, RPi)
- `amd64` - VMs and x64 hardware
- `all` - Architecture-independent packages

27
apt/conf/distributions Normal file
View File

@ -0,0 +1,27 @@
# apt/conf/distributions
# Reprepro configuration for SecuBox-DEB APT repository
# Multi-arch support: arm64 + amd64
Origin: SecuBox
Label: SecuBox-DEB
Suite: stable
Codename: bookworm
Version: 12
Architectures: arm64 amd64 all source
Components: main contrib
Description: SecuBox-DEB packages for Debian Bookworm
SignWith: default
DebIndices: Packages Release . .gz .bz2
DscIndices: Sources Release .gz .bz2
Origin: SecuBox
Label: SecuBox-DEB
Suite: testing
Codename: bookworm-testing
Version: 12
Architectures: arm64 amd64 all source
Components: main contrib
Description: SecuBox-DEB testing packages
SignWith: default
DebIndices: Packages Release . .gz .bz2
DscIndices: Sources Release .gz .bz2

8
apt/conf/incoming Normal file
View File

@ -0,0 +1,8 @@
# apt/conf/incoming
# Reprepro incoming configuration
Name: default
IncomingDir: incoming
TempDir: tmp
Allow: bookworm bookworm-testing
Cleanup: on_deny on_error

6
apt/conf/options Normal file
View File

@ -0,0 +1,6 @@
# apt/conf/options
# Reprepro global options
verbose
ask-passphrase
basedir /srv/apt

32
apt/hooks/lintian-check Executable file
View File

@ -0,0 +1,32 @@
#!/bin/bash
# apt/hooks/lintian-check
# Pre-publish hook to run lintian on packages before adding to repository
# SecuBox-DEB - CyberMind
set -euo pipefail
DEB_FILE="$1"
if [ ! -f "$DEB_FILE" ]; then
echo "ERROR: Package file not found: $DEB_FILE"
exit 1
fi
echo "=== Running lintian on $(basename "$DEB_FILE") ==="
# Run lintian with appropriate checks
# --fail-on error: Fail if there are errors
# --suppress-tags: Suppress some common warnings for our use case
LINTIAN_OPTS="--fail-on error"
LINTIAN_OPTS="$LINTIAN_OPTS --suppress-tags debian-changelog-file-missing-or-wrong-name"
LINTIAN_OPTS="$LINTIAN_OPTS --suppress-tags changelog-file-missing-in-native-package"
LINTIAN_OPTS="$LINTIAN_OPTS --suppress-tags file-missing-in-root-dir"
if lintian $LINTIAN_OPTS "$DEB_FILE"; then
echo "=== Lintian check PASSED ==="
exit 0
else
echo "=== Lintian check FAILED ==="
echo "Package will not be added to repository."
exit 1
fi

View File

@ -92,7 +92,10 @@ func runBuild(cmd *cobra.Command, args []string) error {
// Resolve output directory
outputDir := buildOutput
if !filepath.IsAbs(outputDir) {
cwd, _ := os.Getwd()
cwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("get working directory for output: %w", err)
}
outputDir = filepath.Join(cwd, outputDir)
}

View File

@ -10,6 +10,7 @@ import (
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"time"
@ -33,6 +34,12 @@ const (
progressBarSize = 50
)
// Package-level HTTP client for connection reuse
var httpClient = &http.Client{Timeout: defaultTimeout}
// Version tag regex for validation
var versionTagRe = regexp.MustCompile(`^[a-zA-Z0-9._-]+$`)
// supportedBoards lists boards that have pre-built images
var supportedBoards = []string{
"mochabin",
@ -161,6 +168,10 @@ func runFetch(cmd *cobra.Command, args []string) error {
if checksumAsset != nil {
fmt.Printf("\nVerifying checksum...\n")
if err := verifyChecksum(outputPath, checksumAsset, imageAsset.Name); err != nil {
// Clean up corrupted file
if rmErr := os.Remove(outputPath); rmErr != nil {
fmt.Fprintf(os.Stderr, "warning: could not remove corrupted file: %v\n", rmErr)
}
return fmt.Errorf("checksum verification failed: %w", err)
}
fmt.Printf("Checksum verified OK\n")
@ -234,7 +245,6 @@ func listReleases() error {
func getReleases() ([]GitHubRelease, error) {
url := fmt.Sprintf("%s/repos/%s/%s/releases", githubAPIBase, githubOwner, githubRepo)
client := &http.Client{Timeout: 30 * time.Second}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
@ -242,7 +252,7 @@ func getReleases() ([]GitHubRelease, error) {
req.Header.Set("Accept", "application/vnd.github.v3+json")
req.Header.Set("User-Agent", "secubox-cli/"+version)
resp, err := client.Do(req)
resp, err := httpClient.Do(req)
if err != nil {
return nil, err
}
@ -261,20 +271,23 @@ func getReleases() ([]GitHubRelease, error) {
return releases, nil
}
func getRelease(version string) (*GitHubRelease, error) {
func getRelease(ver string) (*GitHubRelease, error) {
var url string
if version == "" {
if ver == "" {
url = fmt.Sprintf("%s/repos/%s/%s/releases/latest", githubAPIBase, githubOwner, githubRepo)
} else {
// Normalize version tag
tag := version
tag := ver
if !strings.HasPrefix(tag, "v") {
tag = "v" + tag
}
// Validate version format to prevent URL injection
if !versionTagRe.MatchString(tag) {
return nil, fmt.Errorf("invalid version format: %s", ver)
}
url = fmt.Sprintf("%s/repos/%s/%s/releases/tags/%s", githubAPIBase, githubOwner, githubRepo, tag)
}
client := &http.Client{Timeout: 30 * time.Second}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
@ -282,17 +295,17 @@ func getRelease(version string) (*GitHubRelease, error) {
req.Header.Set("Accept", "application/vnd.github.v3+json")
req.Header.Set("User-Agent", "secubox-cli/"+version)
resp, err := client.Do(req)
resp, err := httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusNotFound {
if version == "" {
if ver == "" {
return nil, fmt.Errorf("no releases found")
}
return nil, fmt.Errorf("release %s not found", version)
return nil, fmt.Errorf("release %s not found", ver)
}
if resp.StatusCode != http.StatusOK {
@ -347,8 +360,7 @@ func downloadWithProgress(url, destPath string, totalSize int64) error {
defer out.Close()
// Start download
client := &http.Client{Timeout: defaultTimeout}
resp, err := client.Get(url)
resp, err := httpClient.Get(url)
if err != nil {
return fmt.Errorf("download: %w", err)
}
@ -445,8 +457,7 @@ func (pr *progressReader) printProgress() {
func verifyChecksum(filePath string, checksumAsset *GitHubAsset, imageName string) error {
// Download checksum file
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Get(checksumAsset.BrowserDownloadURL)
resp, err := httpClient.Get(checksumAsset.BrowserDownloadURL)
if err != nil {
return fmt.Errorf("download checksum: %w", err)
}

View File

@ -3,6 +3,7 @@ package cmd
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
@ -225,7 +226,7 @@ type mockReader struct {
func (m *mockReader) Read(p []byte) (int, error) {
if m.pos >= len(m.data) {
return 0, fmt.Errorf("EOF")
return 0, io.EOF
}
n := copy(p, m.data[m.pos:])
m.pos += n

View File

@ -10,18 +10,32 @@ import (
)
var (
cfgFile string
verbose bool
version = "2.8.0"
cfgFile string
verbose bool
version = "2.8.0"
buildTime = "unknown"
commit = "unknown"
)
// SetVersionInfo sets build-time version information
func SetVersionInfo(v, bt, c string) {
if v != "" {
version = v
}
if bt != "" {
buildTime = bt
}
if c != "" {
commit = c
}
}
var rootCmd = &cobra.Command{
Use: "secubox",
Short: "SecuBox Image Generator & Manager",
Long: `SecuBox CLI tool for profile-based image generation,
building, fetching pre-built images, and OTA updates.
Version: ` + version,
building, fetching pre-built images, and OTA updates.`,
Version: version,
}
func Execute() error {
@ -32,6 +46,9 @@ func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default: /etc/secubox/secubox.yaml)")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
// Set version template
rootCmd.SetVersionTemplate(fmt.Sprintf("secubox version %s\nBuild: %s\nCommit: %s\n", version, buildTime, commit))
}
func initConfig() {

View File

@ -17,6 +17,9 @@ import (
// ValidStages lists all valid build stages in order
var ValidStages = []string{"rootfs", "partition", "boot", "compress", "checksums"}
// Package-level compiled regex for partition size parsing
var partitionSizeRe = regexp.MustCompile(`^(\d+)([KMGT])$`)
// Options holds builder configuration
type Options struct {
Manifest *manifest.Manifest
@ -82,8 +85,7 @@ func ParsePartitionSize(size string) (int64, error) {
return 0, fmt.Errorf("empty size string")
}
re := regexp.MustCompile(`^(\d+)([KMGT])$`)
matches := re.FindStringSubmatch(strings.ToUpper(size))
matches := partitionSizeRe.FindStringSubmatch(strings.ToUpper(size))
if len(matches) != 3 {
return 0, fmt.Errorf("invalid size format: %s", size)
}
@ -166,9 +168,11 @@ func (b *Builder) RunStage(stage string) ([]string, error) {
return cmds, nil
}
// execCommand executes a shell command
// execCommand executes a shell command with error handling
func (b *Builder) execCommand(cmd string) error {
c := exec.Command("sh", "-c", cmd)
// Wrap command with 'set -e' for fail-fast behavior
wrappedCmd := fmt.Sprintf("set -e\n%s", cmd)
c := exec.Command("sh", "-c", wrappedCmd)
c.Stdout = os.Stdout
c.Stderr = os.Stderr
return c.Run()

View File

@ -200,50 +200,62 @@ func (b *Builder) stagePartition() ([]string, error) {
imagePath, dataStart, dataEnd,
))
// Set up loop device
cmds = append(cmds, fmt.Sprintf(
"LOOPDEV=$(losetup --find --show --partscan %s)",
imagePath,
))
// Format partitions
cmds = append(cmds, "mkfs.vfat -F 32 -n ESP ${LOOPDEV}p1")
cmds = append(cmds, "mkfs.ext4 -L rootfs ${LOOPDEV}p2")
cmds = append(cmds, "mkfs.ext4 -L data ${LOOPDEV}p3")
// Mount partitions and copy rootfs
// Mount operations as single script with cleanup trap
rootfs := b.rootfsPath()
mntDir := filepath.Join(b.outputDir, "mnt")
cmds = append(cmds, fmt.Sprintf("mkdir -p %s", mntDir))
cmds = append(cmds, fmt.Sprintf("mount ${LOOPDEV}p2 %s", mntDir))
cmds = append(cmds, fmt.Sprintf("mkdir -p %s/boot/efi", mntDir))
cmds = append(cmds, fmt.Sprintf("mount ${LOOPDEV}p1 %s/boot/efi", mntDir))
cmds = append(cmds, fmt.Sprintf("mkdir -p %s/srv", mntDir))
cmds = append(cmds, fmt.Sprintf("mount ${LOOPDEV}p3 %s/srv", mntDir))
// Copy rootfs to image
cmds = append(cmds, fmt.Sprintf(
"cp -a %s/* %s/",
rootfs, mntDir,
))
// Generate fstab
// Generate fstab content
fstab := `# /etc/fstab - SecuBox generated
LABEL=rootfs / ext4 errors=remount-ro 0 1
LABEL=ESP /boot/efi vfat umask=0077 0 2
LABEL=data /srv ext4 defaults 0 2
`
cmds = append(cmds, fmt.Sprintf(
"echo '%s' > %s/etc/fstab",
fstab, mntDir,
))
// Unmount and cleanup
cmds = append(cmds, fmt.Sprintf("umount %s/srv", mntDir))
cmds = append(cmds, fmt.Sprintf("umount %s/boot/efi", mntDir))
cmds = append(cmds, fmt.Sprintf("umount %s", mntDir))
cmds = append(cmds, "losetup -d ${LOOPDEV}")
// Single script with trap for cleanup on failure
mountScript := fmt.Sprintf(`set -e
# Set up loop device
LOOPDEV=$(losetup --find --show --partscan %s)
# Cleanup function
cleanup() {
umount %s/srv 2>/dev/null || true
umount %s/boot/efi 2>/dev/null || true
umount %s 2>/dev/null || true
losetup -d $LOOPDEV 2>/dev/null || true
}
trap cleanup EXIT
# Format partitions
mkfs.vfat -F 32 -n ESP ${LOOPDEV}p1
mkfs.ext4 -L rootfs ${LOOPDEV}p2
mkfs.ext4 -L data ${LOOPDEV}p3
# Mount partitions
mkdir -p %s
mount ${LOOPDEV}p2 %s
mkdir -p %s/boot/efi
mount ${LOOPDEV}p1 %s/boot/efi
mkdir -p %s/srv
mount ${LOOPDEV}p3 %s/srv
# Copy rootfs to image
cp -a %s/* %s/
# Generate fstab
cat > %s/etc/fstab << 'FSTAB_EOF'
%sFSTAB_EOF
# Cleanup runs via trap on exit
`,
imagePath,
mntDir, mntDir, mntDir,
mntDir, mntDir, mntDir, mntDir, mntDir, mntDir,
rootfs, mntDir,
mntDir, fstab,
)
cmds = append(cmds, mountScript)
return cmds, nil
}
@ -258,31 +270,51 @@ func (b *Builder) stageBoot() ([]string, error) {
bootMethod = "uboot" // Default for ARM
}
// Set up loop device again for boot installation
cmds = append(cmds, fmt.Sprintf(
"LOOPDEV=$(losetup --find --show --partscan %s)",
imagePath,
))
mntDir := filepath.Join(b.outputDir, "mnt")
cmds = append(cmds, fmt.Sprintf("mount ${LOOPDEV}p2 %s", mntDir))
cmds = append(cmds, fmt.Sprintf("mount ${LOOPDEV}p1 %s/boot/efi", mntDir))
// Get boot-specific commands
var bootCmds []string
switch bootMethod {
case "uboot":
cmds = append(cmds, b.installUBoot(mntDir)...)
bootCmds = b.installUBoot(mntDir)
case "grub":
cmds = append(cmds, b.installGrub(mntDir)...)
bootCmds = b.installGrub(mntDir)
case "extlinux":
cmds = append(cmds, b.installExtlinux(mntDir)...)
bootCmds = b.installExtlinux(mntDir)
default:
cmds = append(cmds, fmt.Sprintf("# Unknown boot method: %s", bootMethod))
bootCmds = []string{fmt.Sprintf("echo 'Unknown boot method: %s'", bootMethod)}
}
// Unmount
cmds = append(cmds, fmt.Sprintf("umount %s/boot/efi", mntDir))
cmds = append(cmds, fmt.Sprintf("umount %s", mntDir))
cmds = append(cmds, "losetup -d ${LOOPDEV}")
// Single script with trap for cleanup on failure
bootScript := fmt.Sprintf(`set -e
# Set up loop device
LOOPDEV=$(losetup --find --show --partscan %s)
# Cleanup function
cleanup() {
umount %s/boot/efi 2>/dev/null || true
umount %s 2>/dev/null || true
losetup -d $LOOPDEV 2>/dev/null || true
}
trap cleanup EXIT
# Mount partitions
mount ${LOOPDEV}p2 %s
mount ${LOOPDEV}p1 %s/boot/efi
# Boot installation commands
%s
# Cleanup runs via trap on exit
`,
imagePath,
mntDir, mntDir,
mntDir, mntDir,
strings.Join(bootCmds, "\n"),
)
cmds = append(cmds, bootScript)
return cmds, nil
}

View File

@ -11,6 +11,9 @@ import (
"strings"
)
// Package-level compiled regex for performance
var memTotalRe = regexp.MustCompile(`MemTotal:\s+(\d+)\s+kB`)
// Info holds detected hardware information
type Info struct {
Board string
@ -102,6 +105,18 @@ func detectCPU() (string, int) {
}
}
// Check for scanner errors
if err := scanner.Err(); err != nil {
// Log error but continue with what we have
if cores == 0 {
cores = runtime.NumCPU()
}
if model == "" {
model = "unknown"
}
return model, cores
}
// Fallback to runtime if no cores found
if cores == 0 {
cores = runtime.NumCPU()
@ -122,15 +137,23 @@ func detectRAM() uint64 {
defer file.Close()
scanner := bufio.NewScanner(file)
re := regexp.MustCompile(`MemTotal:\s+(\d+)\s+kB`)
for scanner.Scan() {
matches := re.FindStringSubmatch(scanner.Text())
matches := memTotalRe.FindStringSubmatch(scanner.Text())
if len(matches) == 2 {
kb, _ := strconv.ParseUint(matches[1], 10, 64)
kb, err := strconv.ParseUint(matches[1], 10, 64)
if err != nil {
return 0
}
return kb * 1024 // Convert to bytes
}
}
// Check for scanner errors
if err := scanner.Err(); err != nil {
return 0
}
return 0
}

View File

@ -0,0 +1,160 @@
package hardware
import (
"testing"
)
func TestDetect(t *testing.T) {
info, err := Detect()
if err != nil {
t.Fatalf("Detect() error = %v", err)
}
if info == nil {
t.Fatal("Detect() returned nil")
}
// Arch should always be detected
if info.Arch == "" {
t.Error("Arch should not be empty")
}
// CPUCores should be at least 1
if info.CPUCores < 1 {
t.Errorf("CPUCores = %d, want >= 1", info.CPUCores)
}
}
func TestSuggestTier(t *testing.T) {
tests := []struct {
name string
ramTotalMB uint64
want string
}{
{"lite - 1GB", 1024, "tier-lite"},
{"lite - 2GB", 2048, "tier-lite"},
{"standard - 4GB", 4096, "tier-standard"},
{"standard - 6GB", 6144, "tier-standard"},
{"pro - 8GB", 8192, "tier-pro"},
{"pro - 16GB", 16384, "tier-pro"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
info := &Info{RAMTotalMB: tt.ramTotalMB}
got := info.SuggestTier()
if got != tt.want {
t.Errorf("SuggestTier() = %q, want %q", got, tt.want)
}
})
}
}
func TestInfoString(t *testing.T) {
info := &Info{
Board: "mochabin",
Arch: "arm64",
CPUModel: "Test CPU",
CPUCores: 4,
RAMTotalMB: 8192,
}
s := info.String()
if s == "" {
t.Error("String() returned empty string")
}
// Check that key information is present
if !contains(s, "mochabin") {
t.Error("String() should contain board name")
}
if !contains(s, "arm64") {
t.Error("String() should contain arch")
}
if !contains(s, "8192") {
t.Error("String() should contain RAM")
}
}
func TestIdentifyBoardFromModel(t *testing.T) {
tests := []struct {
model string
want string
}{
{"GlobalScale MOCHAbin", "mochabin"},
{"Globalscale ESPRESSObin", "espressobin-v7"},
{"ESPRESSObin Ultra", "espressobin-ultra"},
{"Raspberry Pi 4 Model B", "rpi4"},
{"Raspberry Pi 5", "rpi5"},
{"Raspberry Pi 400", "rpi400"},
{"Marvell Armada 7040", "mochabin"},
{"Marvell Armada 3720", "espressobin-v7"},
{"Unknown Board", ""},
}
for _, tt := range tests {
t.Run(tt.model, func(t *testing.T) {
got := identifyBoardFromModel(tt.model)
if got != tt.want {
t.Errorf("identifyBoardFromModel(%q) = %q, want %q", tt.model, got, tt.want)
}
})
}
}
func TestIdentifyBoardFromCompatible(t *testing.T) {
tests := []struct {
compatible string
want string
}{
{"globalscale,mochabin", "mochabin"},
{"globalscale,espressobin", "espressobin-v7"},
{"raspberrypi,4-model-b", "rpi4"},
{"raspberrypi,400", "rpi400"},
{"unknown,board", ""},
}
for _, tt := range tests {
t.Run(tt.compatible, func(t *testing.T) {
got := identifyBoardFromCompatible(tt.compatible)
if got != tt.want {
t.Errorf("identifyBoardFromCompatible(%q) = %q, want %q", tt.compatible, got, tt.want)
}
})
}
}
func TestIdentifyBoardFromDMI(t *testing.T) {
tests := []struct {
product string
want string
}{
{"VirtualBox", "vm-x64"},
{"VMware Virtual Machine", "vm-x64"},
{"QEMU KVM", "vm-x64"},
{"Dell PowerEdge", ""},
}
for _, tt := range tests {
t.Run(tt.product, func(t *testing.T) {
got := identifyBoardFromDMI(tt.product)
if got != tt.want {
t.Errorf("identifyBoardFromDMI(%q) = %q, want %q", tt.product, got, tt.want)
}
})
}
}
// Helper function
func contains(s, substr string) bool {
return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsSubstr(s, substr))
}
func containsSubstr(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}

View File

@ -58,6 +58,57 @@ type BootState struct {
IsRecovery bool
}
// atomicWriteFile writes data to a file atomically using temp file + rename.
// This prevents partial writes and race conditions.
func atomicWriteFile(path string, data []byte, perm os.FileMode) error {
dir := filepath.Dir(path)
// Create temp file in same directory (required for atomic rename)
tmpFile, err := os.CreateTemp(dir, ".tmp-*")
if err != nil {
return fmt.Errorf("create temp file: %w", err)
}
tmpPath := tmpFile.Name()
// Clean up temp file on any error
defer func() {
if tmpPath != "" {
os.Remove(tmpPath)
}
}()
// Set permissions before writing content
if err := tmpFile.Chmod(perm); err != nil {
tmpFile.Close()
return fmt.Errorf("chmod temp file: %w", err)
}
// Write data
if _, err := tmpFile.Write(data); err != nil {
tmpFile.Close()
return fmt.Errorf("write temp file: %w", err)
}
// Sync to disk
if err := tmpFile.Sync(); err != nil {
tmpFile.Close()
return fmt.Errorf("sync temp file: %w", err)
}
if err := tmpFile.Close(); err != nil {
return fmt.Errorf("close temp file: %w", err)
}
// Atomic rename
if err := os.Rename(tmpPath, path); err != nil {
return fmt.Errorf("rename temp file: %w", err)
}
// Clear tmpPath so defer doesn't try to remove
tmpPath = ""
return nil
}
// GetActiveSlot reads the active boot slot from the boot control file
func GetActiveSlot() (Slot, error) {
data, err := os.ReadFile(ActiveSlotFile)
@ -143,7 +194,7 @@ func GetBootState() (*BootState, error) {
}, nil
}
// SetActiveSlot writes the active slot to the boot control file
// SetActiveSlot writes the active slot to the boot control file atomically
func SetActiveSlot(slot Slot) error {
if slot != SlotA && slot != SlotB {
return fmt.Errorf("invalid slot: %s", slot)
@ -154,14 +205,15 @@ func SetActiveSlot(slot Slot) error {
return fmt.Errorf("create boot control dir: %w", err)
}
if err := os.WriteFile(ActiveSlotFile, []byte(string(slot)+"\n"), 0644); err != nil {
// Use atomic write: write to temp file then rename
if err := atomicWriteFile(ActiveSlotFile, []byte(string(slot)+"\n"), 0600); err != nil {
return fmt.Errorf("write active slot: %w", err)
}
return nil
}
// SetFallbackSlot writes the fallback slot to the boot control file
// SetFallbackSlot writes the fallback slot to the boot control file atomically
func SetFallbackSlot(slot Slot) error {
if slot != SlotA && slot != SlotB {
return fmt.Errorf("invalid slot: %s", slot)
@ -172,14 +224,15 @@ func SetFallbackSlot(slot Slot) error {
return fmt.Errorf("create boot control dir: %w", err)
}
if err := os.WriteFile(FallbackSlotFile, []byte(string(slot)+"\n"), 0644); err != nil {
// Use atomic write with restrictive permissions
if err := atomicWriteFile(FallbackSlotFile, []byte(string(slot)+"\n"), 0600); err != nil {
return fmt.Errorf("write fallback slot: %w", err)
}
return nil
}
// SetBootCount sets the boot count value
// SetBootCount sets the boot count value atomically
func SetBootCount(count int) error {
if count < 0 {
count = 0
@ -190,7 +243,8 @@ func SetBootCount(count int) error {
return fmt.Errorf("create boot control dir: %w", err)
}
if err := os.WriteFile(BootCountFile, []byte(strconv.Itoa(count)+"\n"), 0644); err != nil {
// Use atomic write with restrictive permissions
if err := atomicWriteFile(BootCountFile, []byte(strconv.Itoa(count)+"\n"), 0600); err != nil {
return fmt.Errorf("write boot count: %w", err)
}
@ -312,13 +366,29 @@ func findRootDevice() (string, error) {
// extractBaseDevice extracts the base device from a partition device
// e.g., /dev/mmcblk0p2 -> /dev/mmcblk0
// e.g., /dev/nvme0n1p2 -> /dev/nvme0n1
// e.g., /dev/sda2 -> /dev/sda
// e.g., /dev/loop0p1 -> /dev/loop0
func extractBaseDevice(partDev string) string {
// Handle mmcblk and nvme style (e.g., /dev/mmcblk0p2 -> /dev/mmcblk0)
if strings.Contains(partDev, "mmcblk") || strings.Contains(partDev, "nvme") {
idx := strings.LastIndex(partDev, "p")
if idx > 0 {
return partDev[:idx]
// Handle devices with 'p' separator before partition number
// This includes: mmcblk0p2, nvme0n1p2, loop0p1
if strings.Contains(partDev, "mmcblk") || strings.Contains(partDev, "nvme") || strings.Contains(partDev, "loop") {
// Find the last 'p' followed by digits only
for i := len(partDev) - 1; i >= 0; i-- {
if partDev[i] == 'p' {
// Check if everything after 'p' is digits
suffix := partDev[i+1:]
allDigits := len(suffix) > 0
for _, c := range suffix {
if c < '0' || c > '9' {
allDigits = false
break
}
}
if allDigits {
return partDev[:i]
}
}
}
}

View File

@ -3,6 +3,7 @@ package profile
import (
"fmt"
"os"
"path/filepath"
)
@ -38,6 +39,48 @@ func (m *Merger) Resolve(name string) (*Profile, error) {
return m.resolveWithPath(name, make(map[string]bool))
}
// ResolveWithArch resolves a tier profile with architecture layer inserted.
// Chain: base → arch/<arch> → tier → (optional tweaks)
// This is the recommended method for full profile resolution.
func (m *Merger) ResolveWithArch(tierName, arch string) (*Profile, error) {
// First resolve base
base, err := m.Resolve("base")
if err != nil {
return nil, fmt.Errorf("resolve base: %w", err)
}
// Check if arch profile exists
archProfilePath := filepath.Join(m.profilesDir, "arch", arch+".yaml")
var archProfile *Profile
if _, statErr := os.Stat(archProfilePath); statErr == nil {
// Load arch profile directly (don't follow its inherits, we already have base)
archProfile, err = Load(archProfilePath)
if err != nil {
return nil, fmt.Errorf("load arch profile %s: %w", arch, err)
}
}
// Load tier profile directly (don't follow its inherits, we build chain manually)
tierProfilePath := filepath.Join(m.profilesDir, tierName+".yaml")
tierProfile, err := Load(tierProfilePath)
if err != nil {
return nil, fmt.Errorf("load tier profile %s: %w", tierName, err)
}
// Build merge chain: base → arch → tier
result := base
if archProfile != nil {
result = m.merge(result, archProfile)
}
result = m.merge(result, tierProfile)
// Set final name/description from tier
result.Name = tierProfile.Name
result.Description = tierProfile.Description
return result, nil
}
// resolveWithPath resolves with cycle detection via visited map
func (m *Merger) resolveWithPath(name string, visited map[string]bool) (*Profile, error) {
// Check for circular inheritance

View File

@ -242,6 +242,154 @@ boot:
}
}
func TestResolveWithArch(t *testing.T) {
dir := t.TempDir()
// Create base profile
base := `
name: base
packages:
required:
- secubox-core
kernel:
version: "6.6"
modules:
enable:
- wireguard
sysctl:
net.ipv4.ip_forward: 1
`
if err := os.WriteFile(filepath.Join(dir, "base.yaml"), []byte(base), 0644); err != nil {
t.Fatal(err)
}
// Create arch/arm64 profile
archDir := filepath.Join(dir, "arch")
if err := os.MkdirAll(archDir, 0755); err != nil {
t.Fatal(err)
}
archProfile := `
name: arch-arm64
inherits: base
packages:
required:
- qemu-user-static
kernel:
modules:
enable:
- cpufreq_dt
sysctl:
vm.nr_hugepages: 64
`
if err := os.WriteFile(filepath.Join(archDir, "arm64.yaml"), []byte(archProfile), 0644); err != nil {
t.Fatal(err)
}
// Create tier profile
tier := `
name: tier-pro
inherits: base
packages:
required:
- secubox-full
features:
dpi: inline
`
if err := os.WriteFile(filepath.Join(dir, "tier-pro.yaml"), []byte(tier), 0644); err != nil {
t.Fatal(err)
}
merger := NewMerger(dir)
result, err := merger.ResolveWithArch("tier-pro", "arm64")
if err != nil {
t.Fatalf("ResolveWithArch() error = %v", err)
}
// Should have base + arch + tier packages merged
required := result.Packages.Required
if !contains(required, "secubox-core") {
t.Errorf("missing secubox-core from base")
}
if !contains(required, "qemu-user-static") {
t.Errorf("missing qemu-user-static from arch/arm64")
}
if !contains(required, "secubox-full") {
t.Errorf("missing secubox-full from tier-pro")
}
// Should have merged kernel modules
modules := result.Kernel.Modules.Enable
if !contains(modules, "wireguard") {
t.Errorf("missing wireguard from base")
}
if !contains(modules, "cpufreq_dt") {
t.Errorf("missing cpufreq_dt from arch/arm64")
}
// Should have merged sysctl (arch overrides base for same key)
if result.Sysctl["net.ipv4.ip_forward"] != 1 {
t.Errorf("sysctl net.ipv4.ip_forward = %v, want 1", result.Sysctl["net.ipv4.ip_forward"])
}
if result.Sysctl["vm.nr_hugepages"] != 64 {
t.Errorf("sysctl vm.nr_hugepages = %v, want 64", result.Sysctl["vm.nr_hugepages"])
}
// Final name should be from tier
if result.Name != "tier-pro" {
t.Errorf("Name = %q, want tier-pro", result.Name)
}
}
func TestResolveWithArch_NoArchProfile(t *testing.T) {
dir := t.TempDir()
// Create base profile
base := `
name: base
packages:
required:
- secubox-core
`
if err := os.WriteFile(filepath.Join(dir, "base.yaml"), []byte(base), 0644); err != nil {
t.Fatal(err)
}
// Create tier profile (no arch/ directory)
tier := `
name: tier-lite
inherits: base
packages:
required:
- secubox-crowdsec
`
if err := os.WriteFile(filepath.Join(dir, "tier-lite.yaml"), []byte(tier), 0644); err != nil {
t.Fatal(err)
}
merger := NewMerger(dir)
result, err := merger.ResolveWithArch("tier-lite", "riscv64")
if err != nil {
t.Fatalf("ResolveWithArch() should succeed without arch profile, got error = %v", err)
}
// Should still merge base + tier
if !contains(result.Packages.Required, "secubox-core") {
t.Errorf("missing secubox-core from base")
}
if !contains(result.Packages.Required, "secubox-crowdsec") {
t.Errorf("missing secubox-crowdsec from tier-lite")
}
}
func contains(slice []string, item string) bool {
for _, s := range slice {
if s == item {
return true
}
}
return false
}
func TestLoadTweaks(t *testing.T) {
dir := t.TempDir()
boardDir := filepath.Join(dir, "test-board")

View File

@ -7,7 +7,17 @@ import (
"github.com/CyberMind-FR/secubox-deb/cmd/secubox/cmd"
)
// Build-time variables (set via -ldflags)
var (
Version = "dev"
BuildTime = "unknown"
Commit = "unknown"
)
func main() {
// Pass version info to cmd package
cmd.SetVersionInfo(Version, BuildTime, Commit)
if err := cmd.Execute(); err != nil {
os.Exit(1)
}

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-ad-guard
category: misc
tier: lite
description: "SecuBox Ad Guard Module"
depends:
- secubox-core
api:
socket: /run/secubox/ad-guard.sock
health: /api/v1/ad-guard/health
ui:
path: /srv/secubox/www/ad-guard

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-admin
category: system
tier: lite
description: "SecuBox System Administration Dashboard"
depends:
- secubox-core
api:
socket: /run/secubox/admin.sock
health: /api/v1/admin/health
ui:
path: /srv/secubox/www/admin

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-ai-gateway
category: ai
tier: pro
description: "SecuBox Ai-gateway Module"
depends:
- secubox-core
api:
socket: /run/secubox/ai-gateway.sock
health: /api/v1/ai-gateway/health
ui:
path: /srv/secubox/www/ai-gateway

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-ai-insights
category: ai
tier: pro
description: "SecuBox AI Insights - ML-based Threat Detection"
depends:
- secubox-core
api:
socket: /run/secubox/ai-insights.sock
health: /api/v1/ai-insights/health
ui:
path: /srv/secubox/www/ai-insights

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-auth
category: security
tier: lite
description: "SecuBox Auth Guardian"
depends:
- secubox-core
api:
socket: /run/secubox/auth.sock
health: /api/v1/auth/health
ui:
path: /srv/secubox/www/auth

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-avatar
category: misc
tier: lite
description: "SecuBox Identity and Avatar Manager"
depends:
- secubox-core
api:
socket: /run/secubox/avatar.sock
health: /api/v1/avatar/health
ui:
path: /srv/secubox/www/avatar

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-backup
category: system
tier: lite
description: "SecuBox Backup Module"
depends:
- secubox-core
api:
socket: /run/secubox/backup.sock
health: /api/v1/backup/health
ui:
path: /srv/secubox/www/backup

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-c3box
category: dashboard
tier: lite
description: "SecuBox C3box Module"
depends:
- secubox-core
api:
socket: /run/secubox/c3box.sock
health: /api/v1/c3box/health
ui:
path: /srv/secubox/www/c3box

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-cdn
category: misc
tier: standard
description: "SecuBox CDN Cache"
depends:
- secubox-core
api:
socket: /run/secubox/cdn.sock
health: /api/v1/cdn/health
ui:
path: /srv/secubox/www/cdn

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-cloner
category: misc
tier: lite
description: "SecuBox Cloner - System backup and restore"
depends:
- secubox-core
api:
socket: /run/secubox/cloner.sock
health: /api/v1/cloner/health
ui:
path: /srv/secubox/www/cloner

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-config-advisor
category: system
tier: lite
description: "SecuBox Config-advisor Module"
depends:
- secubox-core
api:
socket: /run/secubox/config-advisor.sock
health: /api/v1/config-advisor/health
ui:
path: /srv/secubox/www/config-advisor

View File

@ -0,0 +1,7 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-console
category: misc
tier: lite
description: "SecuBox Console TUI — Terminal Dashboard"

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-cookies
category: misc
tier: lite
description: "SecuBox Cookie Tracker"
depends:
- secubox-core
api:
socket: /run/secubox/cookies.sock
health: /api/v1/cookies/health
ui:
path: /srv/secubox/www/cookies

View File

@ -1,23 +1,10 @@
# packages/secubox-core/debian/secubox.yaml
# SecuBox package metadata for meta-script generator
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-core
category: core
description:
en: SecuBox core libraries and utilities
fr: Bibliotheques et utilitaires SecuBox
category: system
tier: all
description: "SecuBox-DEB — bibliothèque centrale et infrastructure"
requirements:
min_ram: 128M
arch:
- arm64
- amd64
tags:
- core
- essential
- required
services:
- secubox-core.service
ports: []
ui:
path: /srv/secubox/www/core

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-crowdsec
category: security
tier: lite
description: "SecuBox CrowdSec Dashboard"
depends:
- secubox-core
api:
socket: /run/secubox/crowdsec.sock
health: /api/v1/crowdsec/health
ui:
path: /srv/secubox/www/crowdsec

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-cve-triage
category: security
tier: lite
description: "SecuBox Cve-triage Module"
depends:
- secubox-core
api:
socket: /run/secubox/cve-triage.sock
health: /api/v1/cve-triage/health
ui:
path: /srv/secubox/www/cve-triage

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-cyberfeed
category: misc
tier: lite
description: "SecuBox CyberFeed - Threat Intelligence Feed Aggregator"
depends:
- secubox-core
api:
socket: /run/secubox/cyberfeed.sock
health: /api/v1/cyberfeed/health
ui:
path: /srv/secubox/www/cyberfeed

View File

@ -0,0 +1,7 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-daemon
category: system
tier: lite
description: "SecuBox Mesh Daemon - WireGuard mesh with ZKP authentication"

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-device-intel
category: misc
tier: lite
description: "SecuBox Device-intel Module"
depends:
- secubox-core
api:
socket: /run/secubox/device-intel.sock
health: /api/v1/device-intel/health
ui:
path: /srv/secubox/www/device-intel

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-dns-guard
category: network
tier: lite
description: "SecuBox Dns-guard Module"
depends:
- secubox-core
api:
socket: /run/secubox/dns-guard.sock
health: /api/v1/dns-guard/health
ui:
path: /srv/secubox/www/dns-guard

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-dns-provider
category: network
tier: lite
description: "SecuBox DNS Provider Module"
depends:
- secubox-core
api:
socket: /run/secubox/dns-provider.sock
health: /api/v1/dns-provider/health
ui:
path: /srv/secubox/www/dns-provider

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-dns
category: network
tier: lite
description: "SecuBox Dns Module"
depends:
- secubox-core
api:
socket: /run/secubox/dns.sock
health: /api/v1/dns/health
ui:
path: /srv/secubox/www/dns

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-domoticz
category: misc
tier: lite
description: "Domoticz Home Automation for SecuBox"
depends:
- secubox-core
api:
socket: /run/secubox/domoticz.sock
health: /api/v1/domoticz/health
ui:
path: /srv/secubox/www/domoticz

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-dpi
category: misc
tier: pro
description: "SecuBox DPI — netifyd Dashboard"
depends:
- secubox-core
api:
socket: /run/secubox/dpi.sock
health: /api/v1/dpi/health
ui:
path: /srv/secubox/www/dpi

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-droplet
category: misc
tier: lite
description: "SecuBox Droplet File Publisher"
depends:
- secubox-core
api:
socket: /run/secubox/droplet.sock
health: /api/v1/droplet/health
ui:
path: /srv/secubox/www/droplet

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-exposure
category: misc
tier: lite
description: "SecuBox Exposure Manager"
depends:
- secubox-core
api:
socket: /run/secubox/exposure.sock
health: /api/v1/exposure/health
ui:
path: /srv/secubox/www/exposure

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-eye-remote
category: dashboard
tier: lite
description: "SecuBox Eye Remote USB Gadget Integration"
depends:
- secubox-core
api:
socket: /run/secubox/eye-remote.sock
health: /api/v1/eye-remote/health
ui:
path: /srv/secubox/www/eye-remote

View File

@ -0,0 +1,58 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-full
category: misc
tier: lite
description: "SecuBox Full — All 49 modules"
depends:
- secubox-core
- secubox-hub
- secubox-portal
- secubox-system
- secubox-crowdsec
- secubox-waf
- secubox-vortex-firewall
- secubox-auth
- secubox-nac
- secubox-wireguard
- secubox-mesh
- secubox-p2p
- secubox-netmodes
- secubox-dpi
- secubox-qos
- secubox-traffic
- secubox-vhost
- secubox-haproxy
- secubox-cdn
- secubox-dns
- secubox-vortex-dns
- secubox-meshname
- secubox-netdata
- secubox-mediaflow
- secubox-device-intel
- secubox-watchdog
- secubox-metrics
- secubox-soc
- secubox-roadmap
- secubox-mail
- secubox-mail-lxc
- secubox-webmail
- secubox-webmail-lxc
- secubox-users
- secubox-gitea
- secubox-nextcloud
- secubox-droplet
- secubox-streamlit
- secubox-streamforge
- secubox-metablogizer
- secubox-publish
- secubox-c3box
- secubox-backup
- secubox-tor
- secubox-exposure
- secubox-zkp
- secubox-mitmproxy
- secubox-repo
- secubox-hardening

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-gitea
category: publishing
tier: pro
description: "SecuBox Gitea Module"
depends:
- secubox-core
api:
socket: /run/secubox/gitea.sock
health: /api/v1/gitea/health
ui:
path: /srv/secubox/www/gitea

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-glances
category: misc
tier: lite
description: "SecuBox Glances Dashboard"
depends:
- secubox-core
api:
socket: /run/secubox/glances.sock
health: /api/v1/glances/health
ui:
path: /srv/secubox/www/glances

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-gotosocial
category: dashboard
tier: lite
description: "GoToSocial Fediverse server management"
depends:
- secubox-core
api:
socket: /run/secubox/gotosocial.sock
health: /api/v1/gotosocial/health
ui:
path: /srv/secubox/www/gotosocial

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-haproxy
category: network
tier: standard
description: "SecuBox HAProxy Dashboard"
depends:
- secubox-core
api:
socket: /run/secubox/haproxy.sock
health: /api/v1/haproxy/health
ui:
path: /srv/secubox/www/haproxy

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-hardening
category: security
tier: lite
description: "SecuBox Kernel and System Hardening"
depends:
- secubox-core
api:
socket: /run/secubox/hardening.sock
health: /api/v1/hardening/health
ui:
path: /srv/secubox/www/hardening

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-hexo
category: misc
tier: lite
description: "Hexo static blog generator management"
depends:
- secubox-core
api:
socket: /run/secubox/hexo.sock
health: /api/v1/hexo/health
ui:
path: /srv/secubox/www/hexo

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-homeassistant
category: iot
tier: lite
description: "Home Assistant IoT Hub for SecuBox"
depends:
- secubox-core
api:
socket: /run/secubox/homeassistant.sock
health: /api/v1/homeassistant/health
ui:
path: /srv/secubox/www/homeassistant

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-hub
category: system
tier: all
description: "SecuBox Hub — Tableau de bord central"
depends:
- secubox-core
api:
socket: /run/secubox/hub.sock
health: /api/v1/hub/health
ui:
path: /srv/secubox/www/hub

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-identity
category: misc
tier: lite
description: "SecuBox Identity Module"
depends:
- secubox-core
api:
socket: /run/secubox/identity.sock
health: /api/v1/identity/health
ui:
path: /srv/secubox/www/identity

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-interceptor
category: misc
tier: lite
description: "SecuBox Traffic Interceptor"
depends:
- secubox-core
api:
socket: /run/secubox/interceptor.sock
health: /api/v1/interceptor/health
ui:
path: /srv/secubox/www/interceptor

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-iot-guard
category: iot
tier: lite
description: "SecuBox Iot-guard Module"
depends:
- secubox-core
api:
socket: /run/secubox/iot-guard.sock
health: /api/v1/iot-guard/health
ui:
path: /srv/secubox/www/iot-guard

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-ipblock
category: misc
tier: lite
description: "SecuBox IP Blocklist Manager"
depends:
- secubox-core
api:
socket: /run/secubox/ipblock.sock
health: /api/v1/ipblock/health
ui:
path: /srv/secubox/www/ipblock

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-jabber
category: misc
tier: lite
description: "SecuBox Jabber XMPP Server"
depends:
- secubox-core
api:
socket: /run/secubox/jabber.sock
health: /api/v1/jabber/health
ui:
path: /srv/secubox/www/jabber

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-jellyfin
category: media
tier: pro
description: "Jellyfin media server management"
depends:
- secubox-core
api:
socket: /run/secubox/jellyfin.sock
health: /api/v1/jellyfin/health
ui:
path: /srv/secubox/www/jellyfin

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-jitsi
category: misc
tier: lite
description: "SecuBox Jitsi Meet - Video Conferencing"
depends:
- secubox-core
api:
socket: /run/secubox/jitsi.sock
health: /api/v1/jitsi/health
ui:
path: /srv/secubox/www/jitsi

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-ksm
category: misc
tier: lite
description: "SecuBox KSM Memory Optimization Dashboard"
depends:
- secubox-core
api:
socket: /run/secubox/ksm.sock
health: /api/v1/ksm/health
ui:
path: /srv/secubox/www/ksm

View File

@ -0,0 +1,10 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-led-heartbeat
category: misc
tier: lite
description: "SecuBox LED heartbeat status indicator"
depends:
- secubox-core

View File

@ -0,0 +1,18 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-lite
category: misc
tier: lite
description: "SecuBox Lite — Essential modules for low-RAM devices"
depends:
- secubox-core
- secubox-hub
- secubox-portal
- secubox-crowdsec
- secubox-wireguard
- secubox-netmodes
- secubox-nac
- secubox-system
- secubox-hardening

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-localai
category: misc
tier: lite
description: "SecuBox LocalAI - Self-hosted LLM inference"
depends:
- secubox-core
api:
socket: /run/secubox/localai.sock
health: /api/v1/localai/health
ui:
path: /srv/secubox/www/localai

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-localrecall
category: misc
tier: lite
description: "SecuBox Localrecall Module"
depends:
- secubox-core
api:
socket: /run/secubox/localrecall.sock
health: /api/v1/localrecall/health
ui:
path: /srv/secubox/www/localrecall

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-lyrion
category: misc
tier: lite
description: "Lyrion Music Server (LMS) for SecuBox"
depends:
- secubox-core
api:
socket: /run/secubox/lyrion.sock
health: /api/v1/lyrion/health
ui:
path: /srv/secubox/www/lyrion

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-mac-guard
category: misc
tier: lite
description: "SecuBox MAC Guard - MAC Address Control"
depends:
- secubox-core
api:
socket: /run/secubox/mac-guard.sock
health: /api/v1/mac-guard/health
ui:
path: /srv/secubox/www/mac-guard

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-magicmirror
category: misc
tier: lite
description: "SecuBox MagicMirror management"
depends:
- secubox-core
api:
socket: /run/secubox/magicmirror.sock
health: /api/v1/magicmirror/health
ui:
path: /srv/secubox/www/magicmirror

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-mail-lxc
category: email
tier: lite
description: "SecuBox Mail LXC Container (Backend)"
depends:
- secubox-core
api:
socket: /run/secubox/mail-lxc.sock
health: /api/v1/mail-lxc/health
ui:
path: /srv/secubox/www/mail-lxc

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-mail
category: email
tier: lite
description: "SecuBox Mail Module"
depends:
- secubox-core
api:
socket: /run/secubox/mail.sock
health: /api/v1/mail/health
ui:
path: /srv/secubox/www/mail

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-master-link
category: misc
tier: lite
description: "SecuBox Master-link Module"
depends:
- secubox-core
api:
socket: /run/secubox/master-link.sock
health: /api/v1/master-link/health
ui:
path: /srv/secubox/www/master-link

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-matrix
category: communication
tier: pro
description: "SecuBox Matrix Synapse - Federated Chat Server"
depends:
- secubox-core
api:
socket: /run/secubox/matrix.sock
health: /api/v1/matrix/health
ui:
path: /srv/secubox/www/matrix

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-mcp-server
category: misc
tier: lite
description: "SecuBox Mcp-server Module"
depends:
- secubox-core
api:
socket: /run/secubox/mcp-server.sock
health: /api/v1/mcp-server/health
ui:
path: /srv/secubox/www/mcp-server

View File

@ -0,0 +1,18 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-mediaflow
category: media
tier: lite
description: "SecuBox Media Flow"
depends:
- secubox-core
- secubox-dpi
api:
socket: /run/secubox/mediaflow.sock
health: /api/v1/mediaflow/health
ui:
path: /srv/secubox/www/mediaflow

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-mesh
category: vpn
tier: lite
description: "SecuBox Mesh DNS - Meshname DNS for mesh networks"
depends:
- secubox-core
api:
socket: /run/secubox/mesh.sock
health: /api/v1/mesh/health
ui:
path: /srv/secubox/www/mesh

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-meshname
category: vpn
tier: lite
description: "SecuBox Meshname Module"
depends:
- secubox-core
api:
socket: /run/secubox/meshname.sock
health: /api/v1/meshname/health
ui:
path: /srv/secubox/www/meshname

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-metablogizer
category: publishing
tier: lite
description: "SecuBox MetaBlogizer - Static Site Publisher"
depends:
- secubox-core
api:
socket: /run/secubox/metablogizer.sock
health: /api/v1/metablogizer/health
ui:
path: /srv/secubox/www/metablogizer

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-metabolizer
category: misc
tier: lite
description: "SecuBox Metabolizer - Log processor and analyzer"
depends:
- secubox-core
api:
socket: /run/secubox/metabolizer.sock
health: /api/v1/metabolizer/health
ui:
path: /srv/secubox/www/metabolizer

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-metacatalog
category: misc
tier: lite
description: "SecuBox Metacatalog - Service catalog and registry"
depends:
- secubox-core
api:
socket: /run/secubox/metacatalog.sock
health: /api/v1/metacatalog/health
ui:
path: /srv/secubox/www/metacatalog

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-metoblizer
category: misc
tier: lite
description: "SecuBox centralized log aggregator"
depends:
- secubox-core
api:
socket: /run/secubox/metoblizer.sock
health: /api/v1/metoblizer/health
ui:
path: /srv/secubox/www/metoblizer

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-metrics
category: monitoring
tier: lite
description: "SecuBox Metrics Dashboard"
depends:
- secubox-core
api:
socket: /run/secubox/metrics.sock
health: /api/v1/metrics/health
ui:
path: /srv/secubox/www/metrics

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-mirror
category: misc
tier: lite
description: "SecuBox Mirror/CDN Cache"
depends:
- secubox-core
api:
socket: /run/secubox/mirror.sock
health: /api/v1/mirror/health
ui:
path: /srv/secubox/www/mirror

View File

@ -0,0 +1,18 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-mitmproxy
category: misc
tier: lite
description: "SecuBox Mitmproxy WAF — Web Application Firewall"
depends:
- secubox-core
- secubox-haproxy
api:
socket: /run/secubox/mitmproxy.sock
health: /api/v1/mitmproxy/health
ui:
path: /srv/secubox/www/mitmproxy

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-mmpm
category: misc
tier: lite
description: "SecuBox MMPM - MagicMirror Package Manager"
depends:
- secubox-core
api:
socket: /run/secubox/mmpm.sock
health: /api/v1/mmpm/health
ui:
path: /srv/secubox/www/mmpm

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-modem
category: misc
tier: lite
description: "SecuBox LTE/5G Modem Management"
depends:
- secubox-core
api:
socket: /run/secubox/modem.sock
health: /api/v1/modem/health
ui:
path: /srv/secubox/www/modem

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-mqtt
category: iot
tier: lite
description: "SecuBox MQTT — Mosquitto Broker Management"
depends:
- secubox-core
api:
socket: /run/secubox/mqtt.sock
health: /api/v1/mqtt/health
ui:
path: /srv/secubox/www/mqtt

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-nac
category: security
tier: lite
description: "SecuBox NAC / Client Guardian"
depends:
- secubox-core
api:
socket: /run/secubox/nac.sock
health: /api/v1/nac/health
ui:
path: /srv/secubox/www/nac

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-ndpid
category: misc
tier: lite
description: "SecuBox nDPId Module"
depends:
- secubox-core
api:
socket: /run/secubox/ndpid.sock
health: /api/v1/ndpid/health
ui:
path: /srv/secubox/www/ndpid

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-netdata
category: monitoring
tier: lite
description: "SecuBox Netdata Dashboard"
depends:
- secubox-core
api:
socket: /run/secubox/netdata.sock
health: /api/v1/netdata/health
ui:
path: /srv/secubox/www/netdata

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-netdiag
category: misc
tier: lite
description: "SecuBox Network Diagnostics — Troubleshooting Tools"
depends:
- secubox-core
api:
socket: /run/secubox/netdiag.sock
health: /api/v1/netdiag/health
ui:
path: /srv/secubox/www/netdiag

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-netifyd
category: misc
tier: lite
description: "SecuBox Netifyd - Network Intelligence Daemon Dashboard"
depends:
- secubox-core
api:
socket: /run/secubox/netifyd.sock
health: /api/v1/netifyd/health
ui:
path: /srv/secubox/www/netifyd

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-netmodes
category: network
tier: lite
description: "SecuBox Network Modes"
depends:
- secubox-core
api:
socket: /run/secubox/netmodes.sock
health: /api/v1/netmodes/health
ui:
path: /srv/secubox/www/netmodes

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-nettweak
category: misc
tier: lite
description: "SecuBox Network Tuning Dashboard"
depends:
- secubox-core
api:
socket: /run/secubox/nettweak.sock
health: /api/v1/nettweak/health
ui:
path: /srv/secubox/www/nettweak

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-network-anomaly
category: network
tier: lite
description: "SecuBox Network-anomaly Module"
depends:
- secubox-core
api:
socket: /run/secubox/network-anomaly.sock
health: /api/v1/network-anomaly/health
ui:
path: /srv/secubox/www/network-anomaly

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-newsbin
category: misc
tier: lite
description: "Usenet downloader management for SecuBox"
depends:
- secubox-core
api:
socket: /run/secubox/newsbin.sock
health: /api/v1/newsbin/health
ui:
path: /srv/secubox/www/newsbin

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-nextcloud
category: misc
tier: pro
description: "SecuBox Nextcloud Module"
depends:
- secubox-core
api:
socket: /run/secubox/nextcloud.sock
health: /api/v1/nextcloud/health
ui:
path: /srv/secubox/www/nextcloud

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-ollama
category: ai
tier: pro
description: "Local AI inference with Ollama"
depends:
- secubox-core
api:
socket: /run/secubox/ollama.sock
health: /api/v1/ollama/health
ui:
path: /srv/secubox/www/ollama

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-openclaw
category: misc
tier: lite
description: "SecuBox OpenClaw OSINT Module"
depends:
- secubox-core
api:
socket: /run/secubox/openclaw.sock
health: /api/v1/openclaw/health
ui:
path: /srv/secubox/www/openclaw

View File

@ -0,0 +1,17 @@
# debian/secubox.yaml
# Auto-generated from debian/control
name: secubox-ossec
category: misc
tier: lite
description: "SecuBox OSSEC HIDS integration"
depends:
- secubox-core
api:
socket: /run/secubox/ossec.sock
health: /api/v1/ossec/health
ui:
path: /srv/secubox/www/ossec

Some files were not shown because too many files have changed in this diff Show More