# Airgap Install

## Prerequisites

### Hardware/OS

* Ubuntu 22.04 LTS on all host nodes.
* Airgap policy: Hosts cannot reach the internet but must be able to resolve DNS.

### Network and DNS

* Hostnames and IPs for all nodes.
* VIP for the control plane.
* Ensure local DNS or add /etc/hosts entry for the private registry on all nodes.

### Size Estimates

* **Images:** \~40 GB
* **Packages:** \~266 MB

Steps to download scripts and package/image lists required for later phases.

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

The following scripts and lists must be downloaded on the node that has internet access and will be used to create the APT repository and the private image repository.
{% endhint %}

1. Script to create an apt repo:

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

```bash
curl --user-agent "<USER_AGENT_TOKEN>" https://pf9-airctl.s3.us-west-1.amazonaws.com/latest/sample_scripts/create_apt_repo.sh > create_apt_repo.sh
```

{% endtab %}
{% endtabs %}

2. Script to download all the required APT dependencies

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

```bash
curl --user-agent "<USER_AGENT_TOKEN>" https://pf9-airctl.s3.us-west-1.amazonaws.com/latest/sample_scripts/download_all_deps.sh > download_all_deps.sh
```

{% endtab %}
{% endtabs %}

3. Script to push the images to private repo

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

```bash
curl --user-agent "<USER_AGENT_TOKEN>" https://pf9-airctl.s3.us-west-1.amazonaws.com/latest/sample_scripts/push-images.sh > push-images.sh
```

{% endtab %}
{% endtabs %}

List of images required

1. PCD-V images list:

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

```bash
curl --user-agent "<YOUR_USER_AGENT_TOKEN>" https://pf9-airctl.s3-accelerate.amazonaws.com/latest/pcdv-images.txt > pcdv-list.txt
```

{% endtab %}
{% endtabs %}

2. PCD-K images list

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

```bash
curl --user-agent "<YOUR_USER_AGENT_TOKEN>" https://pf9-airctl.s3-accelerate.amazonaws.com/latest/pcdk-images.txt > pcdk-list.txt
```

{% endtab %}
{% endtabs %}

### DNS Prerequisite for Image and Repo

If the registry hostname is **not resolvable** in customer DNS, add it to /etc/hosts on each node.

## 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
```

{% endtab %}
{% endtabs %}

{% 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
```

{% endtab %}
{% endtabs %}

{% 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
```

{% endtab %}
{% endtabs %}

Restart service:

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

```bash
sudo systemctl restart systemd-timesyncd sudo systemctl enable systemd-timesyncd
```

{% endtab %}
{% endtabs %}

Verify sync:

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

```bash
timedatectl status timedatectl show-timesync --all
```

{% endtab %}
{% endtabs %}

## Download Required APT Package Dependencies

Download and prepare all required package dependencies on an **Ubuntu 22.04 node** where your APT repo will run.

### Script and Dependency List

The script for downloading the required packages and dependency list should be retrieved as part of the prerequisites.

### Steps:

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

```bash
chmod +x download_all_deps.sh ./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 %}

## Set Up APT Repository

The script for setting up the APT repo should be retrieved as part of the prerequisites.

Steps to create an APT repo:

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

```yaml
chmod +x create_apt_repo.sh
```

{% endtab %}
{% endtabs %}

### Option 1 — HTTP (Insecure)

#### Initialize the Repository

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

```bash
sudo ./scripts/create_apt_repo.sh init
```

{% endtab %}
{% endtabs %}

#### Add Packages

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

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

{% endtab %}
{% endtabs %}

#### Configure Clients (on each node)

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

```bash
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 echo "deb [trusted=yes] http://<repo-host>/ stable main" | sudo tee /etc/apt/sources.list.d/private-repo.list sudo apt update
```

{% endtab %}
{% endtabs %}

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

#### Initialize with Self-Signed Cert

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

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

{% endtab %}
{% endtabs %}

#### OR

Initialize with Custom 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
```

{% endtab %}
{% endtabs %}

#### Add Packages

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

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

{% endtab %}
{% endtabs %}

### Configure Clients (Install CA First If Required)

If the repository was created with a **self-signed certificate**, the generated cert will be available at:

`/etc/nginx/ssl/apt_repo.crt`

Copy this certificate to each client node and update the certificate store. If you used your own certificate when creating the APT repo, copy that certificate instead.

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

```bash
# Update CA certificates 
sudo cp /path/to/apt_repo.crt /usr/local/share/ca-certificates/ 
sudo update-ca-certificates 
# 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 https://<repo-host> stable main" | sudo tee /etc/apt/sources.list.d/private-repo.list 
# Refresh repo 
sudo apt update
```

{% endtab %}
{% endtabs %}

## Setup Image Repository

Script to setup a Sample Image Repository on a Node:

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

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

# === Functions ===

function get_input() {
  local PROMPT="$1"
  local VARIABLE_NAME="$2"
  local OUTPUT

  read -r -p "$PROMPT " OUTPUT
  eval "$VARIABLE_NAME='$OUTPUT'"
}

# === Variables ===

get_input "Enter the registry host IP address" "REGISTRY_HOST"
get_input "Enter the registry domain" "REGISTRY_DOMAIN"
get_input "Enter the username" "USERNAME"
get_input "Enter the password" "PASSWORD"

CERT_DIR="$HOME/certs"
AUTH_DIR="$HOME/auth"
DATA_DIR="$HOME/registry-data"
OPENSSL_CONF="$CERT_DIR/openssl.conf"

echo "[INFO] Using registry password: $PASSWORD"

echo "[Step 1] Installing dependencies..."
sudo apt update && sudo apt -y install docker.io jq apache2-utils
sudo chown "$USER" /var/run/docker.sock

echo "[Step 2] Creating certs directory..."
mkdir -p "$CERT_DIR"
cd "$CERT_DIR"

echo "[Step 2.1] Writing OpenSSL config..."
cat << EOF > "$OPENSSL_CONF"
[req]
distinguished_name = req_distinguished_name
prompt             = no
x509_extensions    = ca_x509_extensions

[ca_x509_extensions]
basicConstraints = CA:TRUE
keyUsage         = cRLSign, keyCertSign

[req_distinguished_name]
C   = US
ST  = Washington
L   = Seattle
CN  = CA

[registry]
distinguished_name = registry_distinguished_name
prompt             = no
req_extensions     = registry_req_extensions

[registry_distinguished_name]
C   = US
ST  = Washington
L   = Seattle
CN  = $REGISTRY_DOMAIN

[registry_req_extensions]
basicConstraints     = CA:FALSE
extendedKeyUsage     = clientAuth, serverAuth
keyUsage             = critical, digitalSignature, keyEncipherment
nsCertType           = client, server
nsComment            = "Docker Registry Certificate"
subjectAltName       = DNS:$REGISTRY_DOMAIN
subjectKeyIdentifier = hash
EOF

echo "[Step 2.2] Generating CA key and cert..."
openssl genrsa -out ca.key 4096
openssl req -x509 -new -sha512 -noenc -key ca.key -days 3653 -config "$OPENSSL_CONF" -out ca.crt

echo "[Step 2.3] Generating registry key and cert signed by CA..."
openssl genrsa -out registry.key 4096
openssl req -new -key registry.key -sha256 -config "$OPENSSL_CONF" -section registry -out registry.csr
openssl x509 -req -days 3653 -in registry.csr -copy_extensions copyall -sha256 \
  -CA ca.crt -CAkey ca.key -CAcreateserial -out registry.crt

cd ~

echo "[Step 3] Setting up authentication..."
mkdir -p "$AUTH_DIR"
htpasswd -Bc "$AUTH_DIR/htpasswd" "$USERNAME" <<< "$PASSWORD"

echo "[Step 4] Creating data directory..."
mkdir -p "$DATA_DIR"

echo "[Step 5] Running Docker registry..."
docker rm -f registry || true
docker run -d \
  --restart=always \
  --name registry \
  -v "$CERT_DIR:/certs" \
  -v "$AUTH_DIR:/auth" \
  -v "$DATA_DIR:/var/lib/registry" \
  -e REGISTRY_AUTH=htpasswd \
  -e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \
  -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
  -e REGISTRY_HTTP_ADDR=0.0.0.0:443 \
  -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/registry.crt \
  -e REGISTRY_HTTP_TLS_KEY=/certs/registry.key \
  -p 443:443 \
  registry:2

echo "[Step 6] Adding CA cert to system trust..."
sudo cp "$CERT_DIR/ca.crt" /usr/local/share/ca-certificates
sudo update-ca-certificates

echo "[Step 7] Verifying registry..."
sleep 3
curl -u "$USERNAME:$PASSWORD" "https://$REGISTRY_DOMAIN/v2/_catalog" || true

echo "✅ Private Docker Registry setup complete!"
echo "   URL: https://$REGISTRY_DOMAIN"
echo "   User: $USERNAME"
echo "   Password: $PASSWORD"
```

{% endtab %}
{% endtabs %}

Testing the Registry Setup (on the same registry node):

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

```bash
docker ps -a cat << EOF >> /etc/hosts <ip> registry.pf9.io EOF curl -u admin:Welcome@PF9123 https://registry.pf9.io/v2/_catalog -k sudo cp /home/ubuntu/certs/ca.crt /usr/local/share/ca-certificates sudo update-ca-certificates curl -u admin:<password> https://registry.pf9.io/v2/_catalog
```

{% endtab %}
{% endtabs %}

## Upload Packages to APT Repo

If you’re hosting your own APT repo:

1. Ensure all packages in dependency\_list.txt are present.
2. Regenerate repo metadata (Packages.gz, Release, InRelease) after any changes.

If using the provided script:

From the repo host, at the repo project root

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

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

{% endtab %}
{% endtabs %}

If your .deb files are in another folder:

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

```bash
sudo ./create_apt_repo.sh add-bulk /path/to/your/deb_dir
```

{% endtab %}
{% endtabs %}

The script will:

* Copy all .deb files into /var/www/html/my-private-apt-repo/pool/main/
* Regenerate Packages.gz and Release

#### On all the client nodes:

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

```bash
sudo apt update
```

{% endtab %}
{% endtabs %}

## Upload Images to Private Registry

The script used to pull the required images and push them to the custom registry is downloaded as part of the prerequisites. *(Note: Docker must be installed for this script to run.)*

Additionally, the PCD-V and PCD-K image lists are also downloaded during the prerequisite steps.

## Install OpenSSL

On a **jumphost 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
sudo dpkg -i openssl_3.0.7-1_amd64.deb sudo tee -a /etc/ld.so.conf.d/openssl-3.0.7.conf << EOF /usr/local/ssl/lib64 EOF sudo ldconfig -v sudo sed -i 's#^PATH="\([^"]*\)"#PATH="\1:/usr/local/ssl/bin"#' /etc/environment source /etc/environment sudo ln -sf /usr/local/ssl/bin/openssl /usr/bin/openssl openssl version
```

{% endtab %}
{% endtabs %}

Expected output:

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

## Download Airctl Artifacts

Run on a **jumphost 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.

## Configure and Deploy PCD

Run the installer script:

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

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

{% endtab %}
{% endtabs %}

Create a binary symlink:

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

```bash
sudo ln -sf /opt/pf9/airctl/airctl /usr/local/bin/airctl airctl --version
```

{% endtab %}
{% endtabs %}

Configure Airctl:

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

```bash
airctl configure \ -4 \ -f <fqdn> \ -e 10.149.101.50 \ -i 10.149.101.242,10.149.101.41,10.149.101.172 \ --master-vip4 10.149.101.193 \ -v ens3 \ -r Region1 \ -k 10.149.106.253 \ -n /mnt/gnocchi \ -p hostpath-provisioner \ --custom-registry-url https://registry.pf9.io \ --custom-registry-username admin \ --custom-registry-password "<password>" \ --custom-registry-ca-cert-path /home/ubuntu/jumphost/ca.crt \ --custom-registry-path-overrides \ --enable-pcd-chart-bundle \ --verbose
```

{% endtab %}
{% endtabs %}

Validate configuration:

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

```bash
airctl check --config /opt/pf9/airctl/conf/airctl-config.yaml
```

{% endtab %}
{% endtabs %}

Deploy components:

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

```bash
airctl create-cluster --config /opt/pf9/airctl/conf/airctl-config.yaml --verbose airctl start --config /opt/pf9/airctl/conf/airctl-config.yaml
```

{% endtab %}
{% endtabs %}

## Hypervisor Onboarding

Ensure each host has the private APT configured and prerequisites installed.

* In the DU, navigate to **Infrastructure → Cluster Hosts** and click **Add Hosts**.
* Follow the instructions to onboard.

####
