# Airgap Install

Air-gapped mode is required when deployment nodes cannot directly access the Internet.

### In this mode, you will:

* Host a **private APT repository** for Ubuntu dependencies.
* Host a **private APT repository** for Ubuntu dependencies.
* Download **airctl artifacts** from an Internet-connected system like a jump host and transfer them to your air-gapped environment.

After preparing these components, proceed to the airctl install to configure and deploy PCD using `airctl`.

### Pre-requisites

For management plane host configuration, follow the [pre-requisite](https://docs.platform9.com/private-cloud-director/2025.10/getting-started/self-hosted/self-hosted-pre-requisites) section, except for package updates and OpenSSL installation. These steps will be covered later in this document.

### NTP Configuration (Client-Side)

{% hint style="info" %}
**Info**

An NTP server must already be available and reachable in the customer’s network.

All nodes must be configured as NTP clients to sync time with this server.
{% endhint %}

If NTP is not already configured on the client nodes, follow the steps below to point them to the NTP server.

Point node to the existing NTP server:

{% tabs %}
{% tab title="Bash" %}

```bash
sudo mkdir -p /etc/systemd/timesyncd.conf.d echo "[Time] NTP=<ntp-server-ip-or-fqdn>" | sudo tee /etc/systemd/timesyncd.conf.d/custom.conf
#Restart service:
sudo systemctl restart systemd-timesyncd sudo systemctl enable systemd-timesyncd
#Verify Sync
timedatectl status timedatectl show-timesync --all
```

{% endtab %}
{% endtabs %}

### Set Up Private APT Repository

Download sample scripts and package dependency list on the apt repo host.

{% tabs %}
{% tab title="Bash" %}

```bash
#Script to create a repo
curl --user-agent "<TOKEN>" -O https://pf9-airctl.s3.us-west-1.amazonaws.com/latest/sample_scripts/create_apt_repo.sh
#Script to download pacakge dependencies
curl --user-agent "<TOKEN>" -O https://pf9-airctl.s3.us-west-1.amazonaws.com/latest/sample_scripts/download_all_deps.sh
#Dependency list
curl --user-agent "<TOKEN>" -O https://pf9-airctl.s3.us-west-1.amazonaws.com/latest/dependency_list.txt
```

{% endtab %}
{% endtabs %}

{% tabs %}
{% tab title="Bash" %}

```bash
chmod +x create_apt_repo.sh download_all_deps.sh
```

{% endtab %}
{% endtabs %}

download required packages on your repo hosts which should have internet connectivity.

{% tabs %}
{% tab title="Bash" %}

```bash
./download_all_deps.sh <path-to-dependency-list.txt>
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
**Info**\
If using HTTPS with a custom CA, install the CA into /usr/local/share/ca-certificates and run update-ca-certificates.
{% endhint %}

#### Initialise the repository with the suitable options from the below.

#### Option 1 — HTTP (Insecure)

Initialise the Repository

{% tabs %}
{% tab title="Bash" %}

```bash
sudo ./create_apt_repo.sh init
sudo ./create_apt_repo.sh add-bulk ./deb_packages
```

{% endtab %}
{% endtabs %}

#### Option 2 — HTTPS (Secure, Recommended)

Initialise with Self-Signed Cert

{% tabs %}
{% tab title="Bash" %}

```bash
sudo ./scripts/create_apt_repo.sh init-https repo.local
sudo ./create_apt_repo.sh add-bulk ./deb_packages
```

{% endtab %}
{% endtabs %}

Or if you already have a cert/key:

{% tabs %}
{% tab title="Bash" %}

```bash
sudo ./scripts/create_apt_repo.sh init-https repo.example.com /path/to/cert.pem /path/to/key.pem
sudo ./create_apt_repo.sh add-bulk ./deb_packages
```

{% endtab %}
{% endtabs %}

### Configure each pcd nodes with apt repo:

If self-signed, distribute and trust the CA:

{% tabs %}
{% tab title="Bash" %}

```bash
# Update CA certificates 
sudo cp /path/to/apt_repo.crt /usr/local/share/ca-certificates/ 
sudo update-ca-certificates
```

{% endtab %}
{% endtabs %}

Add apt repo on each PCD hosts including compute hosts.

{% tabs %}
{% tab title="Bash" %}

```bash
# Backup existing sources list
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
sudo mkdir -p /etc/apt/sources.list.d.bak
sudo mv /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d.bak/ 2>/dev/null || true
sudo rm /etc/apt/sources.list
# Add private repo over HTTPS 
echo "deb [trusted=yes] http://<repo-host>/ stable main" | sudo tee /etc/apt/sources.list.d/private-repo.list
sudo apt update
```

{% endtab %}
{% endtabs %}

## Private Docker Registry Setup

Sample script to setup an Image Repository on a Node:

{% tabs %}
{% tab title="Bash" %}

```bash
curl --user-agent "<TOKEN>" -O https://pf9-airctl.s3-accelerate.amazonaws.com/latest/pcdv-images.txt
curl --user-agent "<TOKEN>" -O https://pf9-airctl.s3.us-west-1.amazonaws.com/latest/sample_scripts/setup_registry.sh
curl --user-agent "<TOKEN>" -O https://pf9-airctl.s3.us-west-1.amazonaws.com/latest/push-images.sh
```

{% endtab %}
{% endtabs %}

Run script setup\_registry.sh to create docker registry, provide credentials when prompted.

{% tabs %}
{% tab title="Bash" %}

```bash
chmod +x setup_registry.sh
./setup_registry.sh
```

{% endtab %}
{% endtabs %}

### Upload Images to Private Registry

{% hint style="info" %}
**NOTE**

Docker must be installed for this script to run.
{% endhint %}

#### Configure Docker to Trust the Private Registry

Prior to pushing container images, add the registry’s CA certificate to Docker’s trust store and restart the service:

{% tabs %}
{% tab title="Bash" %}

```bash
sudo mkdir -p /etc/docker/certs.d/<registry_url>:443
sudo cp /usr/local/share/ca-certificates/ca.crt /etc/docker/certs.d/<registry_url>:443/ca-cert.pem
sudo systemctl restart docker
```

{% endtab %}
{% endtabs %}

push images using the recommended script push-images.sh

{% tabs %}
{% tab title="Bash" %}

```bash
./push-images.sh --images-file-path <image_list_path> --private-registry-url <registry_url> --private-registry-username=<registry_username> --private-registry-password=<registry_password>
```

{% endtab %}
{% endtabs %}

## Install OpenSSL

On a **server** with internet access:

{% tabs %}
{% tab title="Bash" %}

```bash
curl --user-agent "<YOUR_USER_AGENT_KEY>" \ https://pf9-airctl.s3-accelerate.amazonaws.com/openssl-smcp-ubuntu/openssl_3.0.7-1_amd64.deb \ --output openssl_3.0.7-1_amd64.deb
```

{% endtab %}
{% endtabs %}

Copy to all nodes and install:

{% tabs %}
{% tab title="Bash" %}

```bash
# Verify the MD5 checksum
md5sum openssl_3.0.7-1_amd64.deb | grep 706caf \
  || { echo "MD5 checksum does not match, exiting."; exit 1; }

# Install the OpenSSL package
sudo dpkg -i openssl_3.0.7-1_amd64.deb \
  || { echo "Failed to install OpenSSL, exiting."; exit 1; }

# Add OpenSSL library path
echo "/usr/local/ssl/lib64" | sudo tee /etc/ld.so.conf.d/openssl-3.0.7.conf

# Refresh dynamic linker cache
sudo ldconfig -v

# Create a symbolic link to the new OpenSSL binary
sudo ln -sf /usr/local/ssl/bin/openssl /usr/bin/openssl

# Verify the OpenSSL version
openssl version | grep 3.0.7 \
  || { echo "OpenSSL version does not match, exiting."; exit 1; }
```

{% endtab %}
{% endtabs %}

Expected output:

`OpenSSL 3.0.7 1 Nov 2022 (Library: OpenSSL 3.0.7 1 Nov 2022)`

#### Package Updates

Install `cgroup-tools` on each cluster nodes

{% tabs %}
{% tab title="Bash" %}

```bash
apt-get update -y && apt-get install cgroup-tools -y
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
**Info**\
At this stage, the private registry and APT repository have been fully configured. Please review the pre-requisite documentation to ensure all required dependencies and conditions are satisfied before proceeding.
{% endhint %}

## Configure and Deploy PCD

{% hint style="info" %}
**DNS Prerequisite for Images and Repo**\
If the registry hostname is **not resolvable** in customer DNS, add it to /etc/hosts on each node.
{% endhint %}

Step 1: Download the Installer Script.

Run on a **server** with internet access:

{% tabs %}
{% tab title="Bash" %}

```bash
curl --user-agent "<YOUR_USER_AGENT_KEY>" \ https://pf9-airctl.s3-accelerate.amazonaws.com/latest/index.txt | \ awk '{print "curl -sS --user-agent \"<YOUR_USER_AGENT_KEY>\" \"https://pf9-airctl.s3-accelerate.amazonaws.com/latest/" $NF "\" -o ${HOME}/" $NF}' | bash
```

{% endtab %}
{% endtabs %}

Copy all fetched artifacts to one of the master nodes.

#### Step 2: Make the Installer Executable

Set the execute permissions on the installation script.

{% tabs %}
{% tab title="Bash" %}

```bash
chmod +x ./install-pcd.sh
```

{% endtab %}
{% endtabs %}

#### Step 3: Run the Installation Script

Execute the installer with the specified version. This runs the installer using the version number found in `version.txt`.

{% tabs %}
{% tab title="Bash" %}

```bash
./install-pcd.sh $(cat version.txt)
```

{% endtab %}
{% endtabs %}

#### Step 4: Add airctl to System Path

Add `airctl` to the system path to use it globally by creating a symlink in `/usr/bin` folder.

{% tabs %}
{% tab title="Bash" %}

```bash
sudo ln -s /opt/pf9/airctl/airctl /usr/bin/airctl
```

{% endtab %}
{% endtabs %}

### Configure airctl

{% hint style="info" %}
**NOTE**\
Copy the `ca.crt` file from the registry to the node where you will run the `airctl` CLI, and provide its path using the `--custom-registry-ca-cert-path` flag when running `airctl configure`.
{% endhint %}

Run the following command to generate a configuration file, which will be used to deploy the Self-hosted Private Cloud Director management cluster.

You can choose between a `single-master` or `multi-master` management cluster, depending on your installation type (POC or production).

{% tabs %}
{% tab title="Bash" %}

```bash
airctl configure \
  -4 \
  -f <fqdn> \
  -e <du-public-ip> \
  -i <comma-separated master node IPs> \
  --master-vip4 <vip-for-nodelet-cluster> \
  -v ens3 \
  -r <du-region-name> \
  -k <nfs-host-ip-for-hostpath-provisioner> \
  -n /mnt/gnocchi \
  -p hostpath-provisioner \
  --custom-registry-url https://registry.pf9.io \
  --custom-registry-username <registru_username> \
  --custom-registry-password "<registry_password>" \
  --custom-registry-ca-cert-path <path-to-registries-ca.crt> \
  --custom-registry-path-overrides \
  --enable-pcd-chart-bundle \
  --verbose
```

{% endtab %}
{% endtabs %}

You have now completed the airgap–specific configuration. From this point onward, follow the standard steps after `airctl configure` as documented in the [*Install*](https://docs.platform9.com/private-cloud-director/2025.10/getting-started/self-hosted/self-hosted-install) section.
