How CasaOS Implements Custom Startup Scripts in the start.d Directory

CasaOS executes user-provided startup scripts from the start.d directory by resolving the configuration path, discovering executable files, and running them in lexicographic order via exec.CommandContext while logging output and continuing past individual failures.

CasaOS supports extensibility through custom startup scripts placed in a special start.d directory, allowing users to run initialization logic without modifying core server code. This mechanism follows Unix rc.d conventions and is implemented in the IceWhaleTech/CasaOS repository through a dedicated execution pipeline. When the CasaOS service starts, it automatically discovers and runs these scripts after binding the HTTP server but before notifying systemd that the service is ready.

Resolving the start.d Directory Path

CasaOS determines the location of the startup script directory by joining the base configuration path with the subdirectory name start.d. In main.go (lines 200-203), the server constructs this path using the constants.DefaultConfigPath constant defined in internal/conf/var.go.

scriptDirectory := filepath.Join(constants.DefaultConfigPath, "start.d")

The DefaultConfigPath typically resolves to /etc/casaos or $HOME/.config/casaos depending on the installation context. This absolute path is then passed to the execution helper to begin the script discovery process.

The Script Execution Pipeline

The ExecuteScripts function in internal/command/execute.go orchestrates the discovery and execution of custom startup scripts. This implementation provides a robust, fault-tolerant mechanism for running user-provided initialization code.

Script Discovery and Ordering

The function reads all entries in the start.d directory, filters out non-executable files, and sorts the remaining entries lexicographically. This deterministic ordering ensures scripts run in a predictable sequence based on filename (e.g., 01-init.sh before 02-services.sh).

Process Spawning and Environment Inheritance

For each discovered script, CasaOS spawns a new process using exec.CommandContext. The child process inherits the CasaOS environment variables, and both stdout and stderr streams are piped directly to the CasaOS logger for centralized observability.

Error Handling Strategy

If a script exits with a non-zero status code, the error is logged but the execution loop continues. This design prevents a single failing script from blocking subsequent initialization steps or preventing the CasaOS service from starting.

Execution Timing and Service Lifecycle

The call to command.ExecuteScripts(scriptDirectory) occurs at a specific point in the boot sequence:

// In main.go - after HTTP server binds, before systemd notification
command.ExecuteScripts(scriptDirectory)

This timing is critical: scripts run after the HTTP server has bound to its port and the service URL file (casaos.url) has been written, but before the system notifies systemd that the service is ready. Consequently, startup scripts can safely interact with the running CasaOS instance to register additional routes, mount external drives, or launch background daemons.

Creating Custom Startup Scripts

Any executable file placed in the start.d directory will be executed. CasaOS does not restrict scripts to specific languages, supporting shell scripts, compiled binaries, Python programs, or any other executable format.

Bash Script Example

Create a shell script to mount an external drive at startup:


# ~/casaos-config/start.d/01-mount-external.sh

#!/usr/bin/env bash

# Example: mount an external drive at startup

mount /dev/sdb1 /mnt/external && echo "External drive mounted"

Make the script executable:

chmod +x ~/casaos-config/start.d/01-mount-external.sh

Compiled Go Binary Example

You can also use compiled languages. This Go program performs cleanup operations:

// ~/casaos-config/start.d/02-cleanup.go
package main

import (
    "log"
    "os"
)

func main() {
    log.Println("Running custom cleanup...")
    // custom logic here
    _ = os.RemoveAll("/tmp/casaos-temp")
}

Build and mark as executable:

go build -o ~/casaos-config/start.d/02-cleanup ~/casaos-config/start.d/02-cleanup.go
chmod +x ~/casaos-config/start.d/02-cleanup

When CasaOS starts, it executes 01-mount-external.sh followed by 02-cleanup based on lexicographic ordering.

Summary

  • CasaOS resolves the startup script path by joining constants.DefaultConfigPath with "start.d" in main.go.
  • The ExecuteScripts function in internal/command/execute.go handles discovery, sorting, and execution of all executable files in the directory.
  • Scripts run in lexicographic order after the HTTP server binds but before systemd receives the ready notification.
  • Non-zero exit codes are logged but do not halt the execution of subsequent scripts.
  • Any executable format is supported, including Bash scripts and compiled Go binaries.

Frequently Asked Questions

What file permissions are required for scripts in the start.d directory?

Scripts must be marked as executable (typically chmod +x). The ExecuteScripts function explicitly filters out non-executable files during the discovery phase, so files without executable permissions will be ignored even if they contain valid code.

What happens if a startup script fails or returns an error?

CasaOS logs the error and continues executing the remaining scripts. The error handling logic in internal/command/execute.go ensures that a failing script does not prevent other initialization routines from running or block the CasaOS service from completing its startup sequence.

Can I use programming languages other than Bash for startup scripts?

Yes. CasaOS treats any executable file as a valid startup script. You can use Python, Go, Rust, Node.js, or any other language provided the file has the executable bit set and includes the appropriate shebang line (for interpreted languages) or is compiled to a native binary.

Where is the start.d directory located on my system?

The location depends on your CasaOS configuration. According to internal/conf/var.go, the path is constructed by appending start.d to constants.DefaultConfigPath, which typically resolves to /etc/casaos/start.d for system-wide installations or ~/.config/casaos/start.d for user-specific configurations. Check your conf/conf.conf.sample for the exact path in your deployment.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →