# Custom Cloud-Init Scripts

Automate your VPS setup by providing a custom cloud-init script during creation. Your script runs after the platform finishes configuring networking, security, and SSH access.

## Overview

Custom cloud-init lets you:

- Install packages automatically on first boot
- Configure services and daemons
- Set up users and SSH keys
- Deploy applications
- Run any shell commands as root

Scripts run **once** during the initial boot (or after a reinstall) and execute as the `root` user.

## Supported Formats

### Shell Script

Write a standard shell script. The platform saves it to `/tmp/custom-cloud-init.sh` and runs it with `bash` after all platform initialization is complete.

```bash
#!/bin/bash
set -e

apt update -y
apt install -y nginx certbot python3-certbot-nginx

systemctl enable nginx
systemctl start nginx
```

### Cloud-Config YAML

Provide a `#cloud-config` YAML document. The platform merges supported sections into its own cloud-init configuration. Your commands always run last.

```yaml
#cloud-config
packages:
  - nginx
  - certbot

runcmd:
  - systemctl enable nginx
  - systemctl start nginx

write_files:
  - path: /etc/motd
    content: "Welcome to my server!\n"
```

**Supported cloud-config sections:**

| Section | Description |
|---------|-------------|
| `packages` | APT/DNF packages to install |
| `runcmd` | Commands to run (after platform init) |
| `write_files` | Files to create on disk |
| `bootcmd` | Commands to run very early in boot |

## Validation & Warnings

When you enter a script, the platform runs a **warning analyzer** that flags likely mistakes before you deploy — for example a missing `#!` shebang or `#cloud-config` header, interactive commands that could hang the boot (such as `apt` without `-y`), or enabling a restrictive firewall that would cut off DNS and package downloads. Review any warnings and fix them before creating or reinstalling the VPS, since the script only runs on first boot.

## Limits

- Maximum **10,000 characters**
- Only runs on **first boot** (or reinstall)
- Must be compatible with your chosen OS (e.g., `apt` for Ubuntu/Debian, `dnf` for Fedora/AlmaLinux)

## Execution Order

Your custom script runs at the end of the cloud-init lifecycle:

1. Platform configures networking (public IP, gateway, DNS)
2. Platform hardens security (SSH, fail2ban, firewall rules)
3. Platform sets up monitoring agent (qemu-guest-agent)
4. Marketplace app scripts run (if applicable)
5. **Your custom cloud-init runs here**
6. Cloud-init marks as `done`

## Monitoring Progress

When you provide a custom cloud-init script, the platform installs a `cloud-init-progress` helper on your VPS.

### Login Banner

When you SSH in while cloud-init is still running, you'll see:

```
==============================================
  System initialization is in progress...
  Package installations may be running.
  Please wait before using apt/dnf/apk.

  Run: cloud-init status --wait
  Run: cloud-init-progress
==============================================
```

### cloud-init-progress Command

Run `cloud-init-progress` to monitor your script's execution:

```bash
cloud-init-progress
```

**While running** — streams the cloud-init output log in real time (press `Ctrl+C` to stop watching):

```
==============================================
  Cloud-Init Progress Monitor
==============================================

  Status: RUNNING
  Following cloud-init output live...
  Press Ctrl+C to stop watching.

  ----------------------------------------

Setting up nginx (1.24.0-2ubuntu7) ...
Processing triggers for man-db ...
...
```

**When complete** — shows final status and the last lines of the log:

```
==============================================
  Cloud-Init Progress Monitor
==============================================

  Status: COMPLETED SUCCESSFULLY

  Last 10 lines of cloud-init log:
  ----------------------------------------
  Setting up certbot (2.9.0-1) ...
  Created symlink ...
  Cloud-init v. 25.2 finished at Mon, 09 Mar ...

==============================================
```

**If errors occurred** — shows the error status and points you to the full log:

```
  Status: COMPLETED WITH ERRORS

  Full log: /var/log/cloud-init-output.log
  Errors:   cloud-init status --long
```

### Other Useful Commands

| Command | Description |
|---------|-------------|
| `cloud-init status` | Show current cloud-init status (running/done/error) |
| `cloud-init status --wait` | Wait for custom setup and first-boot security hardening to finish |
| `cloud-init status --long` | Show detailed status with error messages |
| `cat /var/log/cloud-init-output.log` | View full cloud-init output log |
| `cat /var/log/cloud-init.log` | View cloud-init internal debug log |
| `dd-hardening-status` | Show first-boot security setup progress |

### Installing Packages After First Boot

SSH becomes available before initialization finishes. On newly provisioned VPS instances, security package installation and upgrades run after marketplace and custom scripts, and cloud-init waits for them to finish. Before installing packages yourself, run:

```bash
cloud-init status --wait
sudo apt-get install curl
```

If you paused security setup with `dd-hardening-pause`, run `sudo dd-hardening-resume` so initialization can finish. Use `dd-hardening-status` to check progress; recent provisioning scripts record the hardening log at `/var/log/dd-hardening.log`.

VPS instances created with older provisioning scripts may report `status: done` while detached security setup still holds the package-manager lock. On those instances, check `dd-hardening-status` if available and let package operations finish before retrying. Older scripts log to `/tmp/security-hardening.log`. Do not delete the dpkg lock files.

## Examples

### Docker Installation

```bash
#!/bin/bash
set -e

curl -fsSL https://get.docker.com | sh
systemctl enable docker
systemctl start docker

# Install Docker Compose
apt install -y docker-compose-plugin

# Allow non-root docker usage (if you create a user)
# usermod -aG docker yourusername
```

### K3s (Lightweight Kubernetes)

```bash
#!/bin/bash
set -e

curl -sfL https://get.k3s.io | sh -

# Wait for K3s to be ready
kubectl wait --for=condition=Ready node --all --timeout=120s
```

### LAMP Stack

```bash
#!/bin/bash
set -e

export DEBIAN_FRONTEND=noninteractive

apt update -y
apt install -y apache2 mysql-server php php-mysql libapache2-mod-php

systemctl enable apache2 mysql
systemctl start apache2 mysql
```

### Node.js Application

```yaml
#cloud-config
packages:
  - curl
  - git

runcmd:
  - curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
  - apt install -y nodejs
  - npm install -g pm2
```

## Tips

- **Use `set -e`** in shell scripts so the script stops on the first error rather than continuing with a broken state.
- **Use `export DEBIAN_FRONTEND=noninteractive`** to prevent `apt` from prompting for user input (which would hang cloud-init).
- **Don't enable UFW/firewalld** unless you understand the platform networking. The platform already configures routes for internal platform services and DNS. A restrictive firewall can break DNS resolution and package downloads.
- **Long scripts are fine** — there is no hard timeout on cloud-init execution. A full desktop environment install may take 10-20 minutes on a small VPS.
- **Test on a cheap instance first** — use a Nano plan to verify your script works before deploying to production.

## Troubleshooting

### Script didn't run

1. Verify the VPS status is `Running`
2. SSH in and check: `cloud-init status --long`
3. Look for errors: `cat /var/log/cloud-init.log | grep -i error`

### Script failed partway

1. Check the output log: `cat /var/log/cloud-init-output.log`
2. The log shows exactly which command failed and why
3. Fix the script and **reinstall** the VPS (cloud-init only runs once)

### Packages failed to install

- Ensure you used the correct package manager for your OS
- Check that `apt update` or `dnf makecache` runs before installing
- Verify the package names are correct for your OS version

### Can't reach the internet during script

- Don't enable UFW or iptables rules that block outbound traffic
- The platform configures the VPS to use its internal DNS resolver — don't overwrite `/etc/resolv.conf`

## Next Steps

- [Creating VPS](https://docs.danubedata.ro/vps-creating) — Full VPS creation guide
- [Managing VPS](https://docs.danubedata.ro/vps-managing) — Post-creation management
- [SSH Access](https://docs.danubedata.ro/vps-ssh) — Connecting to your VPS
- [Terraform Provider](https://docs.danubedata.ro/terraform-resources) — Automate VPS creation with `custom_cloud_init`
