support for AWS marketplace (#477)

This commit is contained in:
Kyle Spearrin
2026-03-27 11:26:10 -04:00
committed by GitHub
parent f7a22a77d5
commit e3dbc253e1
13 changed files with 691 additions and 0 deletions

2
.github/CODEOWNERS vendored
View File

@@ -18,5 +18,7 @@
bitwarden-lite/ @bitwarden/dept-shot
# Release workflows
.github/workflows/release-aws.yml @bitwarden/dept-bre
.github/workflows/release-azure.yml @bitwarden/dept-bre
.github/workflows/release-digital-ocean.yml @bitwarden/dept-bre
.github/workflows/release.yml @bitwarden/dept-bre

168
.github/workflows/release-aws.yml vendored Normal file
View File

@@ -0,0 +1,168 @@
name: Release AWS Marketplace
on:
release:
types: [published]
push:
paths:
- "AWSMarketplace/**"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
permissions:
contents: read
jobs:
build-image:
name: Build Image
runs-on: ubuntu-24.04
timeout-minutes: 90
permissions:
contents: read
id-token: write
steps:
- name: Checkout repo
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: Log in to Azure
uses: bitwarden/gh-actions/azure-login@main
with:
subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
tenant_id: ${{ secrets.AZURE_TENANT_ID }}
client_id: ${{ secrets.AZURE_CLIENT_ID }}
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@main
with:
keyvault: "gh-self-host"
secrets: "aws-marketplace-access-key-id,
aws-marketplace-secret-access-key"
- name: Log out from Azure
uses: bitwarden/gh-actions/azure-logout@main
- name: Set version from version.json
id: set-version
run: |
VERSION=$(jq -r '.versions.coreVersion' version.json)
if [[ -z "$VERSION" ]]; then
echo "ERROR: Failed to extract coreVersion from version.json"
exit 1
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Set up Hashicorp Packer
uses: hashicorp/setup-packer@1aa358be5cf73883762b302a3a03abd66e75b232 # v3.1.0
- name: Build AWS Image
env:
AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.aws-marketplace-access-key-id }}
AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.aws-marketplace-secret-access-key }}
AWS_DEFAULT_REGION: "us-east-1"
AWS_IMG_VERSION: ${{ steps.set-version.outputs.version }}
working-directory: ./AWSMarketplace
run: |
packer version
packer init -upgrade marketplace-image.pkr.hcl
packer build marketplace-image.pkr.hcl
- name: Cleanup orphaned instances on cancellation or failure
if: cancelled() || failure()
env:
AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.aws-marketplace-access-key-id }}
AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.aws-marketplace-secret-access-key }}
AWS_DEFAULT_REGION: "us-east-1"
run: |
echo "Workflow cancelled - cleaning up any orphaned resources..."
echo "## :warning: Workflow Cancelled - Resource Cleanup" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Searching for orphaned instances with tag: \`github-run-${{ github.run_id }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Find and terminate EC2 instances tagged with this run
INSTANCE_IDS=$(aws ec2 describe-instances \
--filters "Name=tag:GitHub_Run,Values=github-run-${{ github.run_id }}" \
"Name=instance-state-name,Values=pending,running,stopping,stopped" \
--query "Reservations[].Instances[].InstanceId" \
--output text)
if [ -n "$INSTANCE_IDS" ]; then
echo "Found orphaned instances: $INSTANCE_IDS"
echo "### Orphaned Instances Found and Terminated" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Instance ID | Status |" >> $GITHUB_STEP_SUMMARY
echo "|-------------|--------|" >> $GITHUB_STEP_SUMMARY
for INSTANCE_ID in $INSTANCE_IDS; do
echo "Terminating instance: $INSTANCE_ID"
if aws ec2 terminate-instances --instance-ids "$INSTANCE_ID"; then
echo "| $INSTANCE_ID | :white_check_mark: Terminated |" >> $GITHUB_STEP_SUMMARY
else
echo "| $INSTANCE_ID | :x: Failed to terminate |" >> $GITHUB_STEP_SUMMARY
fi
done
else
echo "No orphaned instances found"
echo ":white_check_mark: No orphaned resources found - nothing to clean up" >> $GITHUB_STEP_SUMMARY
fi
- name: Add build summary
if: success()
env:
VERSION: ${{ steps.set-version.outputs.version }}
working-directory: ./AWSMarketplace
run: |
echo "## :rocket: AWS Marketplace Image Build Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Version:** ${VERSION}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Get artifact details from manifest
if [ -f manifest.json ]; then
AMI_ID=$(jq -r '.builds[-1].artifact_id' manifest.json)
echo "**AMI ID:** \`$AMI_ID\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi
echo ":white_check_mark: Packer build instance was automatically cleaned up" >> $GITHUB_STEP_SUMMARY
- name: AWS Image Cleanup
working-directory: ./AWSMarketplace
if: ${{ github.event_name != 'release' && github.event_name != 'workflow_dispatch' }}
env:
AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.aws-marketplace-access-key-id }}
AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.aws-marketplace-secret-access-key }}
AWS_DEFAULT_REGION: "us-east-1"
run: |
# Get the AMI ID from the manifest (format: "us-east-1:ami-xxxxxxxxx")
AMI_ID=$(jq -r '.builds[-1].artifact_id' manifest.json | cut -d ":" -f2)
if [ -n "$AMI_ID" ]; then
# Find associated snapshots before deregistering
SNAPSHOT_IDS=$(aws ec2 describe-images --image-ids "$AMI_ID" \
--query "Images[].BlockDeviceMappings[].Ebs.SnapshotId" \
--output text 2>/dev/null || true)
# Deregister the AMI
aws ec2 deregister-image --image-id "$AMI_ID" 2>/dev/null || true
# Delete associated snapshots
for SNAPSHOT_ID in $SNAPSHOT_IDS; do
aws ec2 delete-snapshot --snapshot-id "$SNAPSHOT_ID" 2>/dev/null || true
done
fi
# Update summary for non-release builds
echo "" >> $GITHUB_STEP_SUMMARY
echo "---" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo ":wastebasket: **Non-release build:** AMI \`$AMI_ID\` and snapshots were automatically cleaned up" >> $GITHUB_STEP_SUMMARY

View File

@@ -529,3 +529,18 @@ jobs:
ref: process.env.RELEASE_TAG,
inputs: {}
});
- name: Trigger release-aws workflow
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
RELEASE_TAG: v${{ inputs.release_version }}
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
await github.rest.actions.createWorkflowDispatch({
owner: 'bitwarden',
repo: 'self-host',
workflow_id: 'release-aws.yml',
ref: process.env.RELEASE_TAG,
inputs: {}
});

View File

@@ -0,0 +1,16 @@
#!/bin/bash
#
# First-login trigger for Bitwarden installation.
# This script runs once on the first interactive login, then removes itself.
# Skip for the bitwarden service account (it doesn't have sudo).
if [ "$(whoami)" = "bitwarden" ]; then
return 0 2>/dev/null || exit 0
fi
if [ -f /opt/bitwarden/install-bitwarden.sh ]; then
# Wait for cloud-init to finish (downloads bitwarden.sh on first boot)
echo "Waiting for cloud-init to complete..."
sudo cloud-init status --wait > /dev/null 2>&1
sudo /opt/bitwarden/install-bitwarden.sh
fi

View File

@@ -0,0 +1,4 @@
[Bitwarden]
title=Bitwarden server
description=Bitwarden is an open source password management tool that allows you to securely store, share, and sync passwords and other senitive data.
ports=80/tcp|443/tcp

View File

@@ -0,0 +1,26 @@
#!/bin/sh
#
# Configured as part of the AWS Marketplace Image build process
myip=$(hostname -I | awk '{print$1}')
cat <<EOF
********************************************************************************
Welcome to your Bitwarden server
https://bitwarden.com
Complete documentation:
https://help.bitwarden.com/hosting/
Configuration:
Configuration changes can be made in /home/bitwarden/bwdata/config.yml and
/home/bitwarden/bwdata/env/global.override.env
Common commands:
sudo -i -u bitwarden /home/bitwarden/bitwarden.sh stop
sudo -i -u bitwarden /home/bitwarden/bitwarden.sh restart
sudo -i -u bitwarden /home/bitwarden/bitwarden.sh rebuild
********************************************************************************
To delete this message of the day: rm -rf $(readlink -f ${0})
EOF

View File

@@ -0,0 +1,48 @@
#!/bin/bash
#
# Install Bitwarden
# ref: https://help.bitwarden.com/article/install-on-premise/
#
echo -e ''
echo -e 'Installing Bitwarden...'
echo -e ''
sudo -i -u bitwarden /home/bitwarden/bitwarden.sh install
echo -e ''
echo -e 'Starting Bitwarden containers...'
echo -e ''
sudo -i -u bitwarden /home/bitwarden/bitwarden.sh start
echo -e ''
echo -e 'Waiting for Bitwarden database container to come online...'
sleep 30s
echo -e 'Initializing Bitwarden database...'
echo -e ''
sudo -i -u bitwarden /home/bitwarden/bitwarden.sh updatedb
echo -e ''
echo -e 'Bitwarden installation complete.'
echo -e ''
#
# Setup Bitwarden update cron
# ref: https://help.bitwarden.com/article/updating-on-premise/
#
echo -e '#!/usr/bin/env bash\nsudo -i -u bitwarden /home/bitwarden/bitwarden.sh updateself\nsudo -i -u bitwarden /home/bitwarden/bitwarden.sh update' \
> /etc/cron.weekly/bitwardenupdate
chmod +x /etc/cron.weekly/bitwardenupdate
#
# Cleanup - remove the login trigger
#
rm -f /etc/profile.d/bitwarden-install.sh

View File

@@ -0,0 +1,17 @@
#!/bin/bash
# Scripts in this directory will be executed by cloud-init on the first boot of instances
# created from your image. Things like generating passwords, configuration requiring IP address
# or other items that will be unique to each instance should be done in scripts here.
#
# Setup Bitwarden Installer
# ref: https://help.bitwarden.com/article/install-on-premise/
#
docker pull ghcr.io/bitwarden/setup
curl -L -s -o /home/bitwarden/bitwarden.sh "https://func.bitwarden.com/api/dl/?app=self-host&platform=linux"
chmod +x /home/bitwarden/bitwarden.sh
chown bitwarden:bitwarden /home/bitwarden/bitwarden.sh

View File

@@ -0,0 +1,169 @@
packer {
required_plugins {
amazon = {
version = ">= 1.2.0"
source = "github.com/hashicorp/amazon"
}
}
}
variable "application_name" {
type = string
default = "Bitwarden"
}
variable "application_version" {
type = string
default = "${env("AWS_IMG_VERSION")}"
}
variable "apt_packages" {
type = string
default = "fail2ban ca-certificates curl gnupg"
}
variable "docker_packages" {
type = string
default = "docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin"
}
variable "aws_region" {
type = string
default = "us-east-1"
}
variable "github_run_id" {
type = string
default = "${env("GITHUB_RUN_ID")}"
}
# "timestamp" template function replacement
locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") }
locals {
image_name = "bitwarden-22-04-${local.timestamp}"
}
source "amazon-ebs" "bitwarden_self_host" {
region = var.aws_region
instance_type = "t3.small"
ssh_username = "ubuntu"
source_ami_filter {
filters = {
name = "ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"
root-device-type = "ebs"
virtualization-type = "hvm"
}
most_recent = true
owners = ["099720109477"] # Canonical
}
ami_name = local.image_name
ami_description = "Bitwarden Self-Host ${var.application_version}"
tags = {
Name = local.image_name
Application = "bitwarden-packer-build"
Version = var.application_version
GitHub_Run = "github-run-${var.github_run_id}"
}
run_tags = {
Name = "packer-bitwarden-${var.github_run_id}"
Application = "bitwarden-packer-build"
GitHub_Run = "github-run-${var.github_run_id}"
}
}
build {
sources = ["source.amazon-ebs.bitwarden_self_host"]
provisioner "shell" {
inline = ["cloud-init status --wait"]
}
# Upload individual files to /tmp staging area (amazon-ebs connects as a non-root user)
provisioner "file" {
source = "files/etc/update-motd.d/99-bitwarden-welcome"
destination = "/tmp/99-bitwarden-welcome"
}
provisioner "file" {
source = "files/etc/ufw/applications.d/bitwarden"
destination = "/tmp/bitwarden-ufw"
}
provisioner "file" {
source = "files/opt/bitwarden/install-bitwarden.sh"
destination = "/tmp/install-bitwarden.sh"
}
provisioner "file" {
source = "files/var/lib/cloud/scripts/per-instance/001_onboot"
destination = "/tmp/001_onboot"
}
provisioner "file" {
source = "files/etc/profile.d/bitwarden-install.sh"
destination = "/tmp/bitwarden-install.sh"
}
# Move staged files to their final system locations
provisioner "shell" {
inline = [
"sudo mkdir -p /etc/update-motd.d /etc/ufw/applications.d /opt/bitwarden /var/lib/cloud/scripts/per-instance",
"sudo mv /tmp/99-bitwarden-welcome /etc/update-motd.d/99-bitwarden-welcome",
"sudo mv /tmp/bitwarden-ufw /etc/ufw/applications.d/bitwarden",
"sudo mv /tmp/install-bitwarden.sh /opt/bitwarden/install-bitwarden.sh",
"sudo mv /tmp/001_onboot /var/lib/cloud/scripts/per-instance/001_onboot",
"sudo mv /tmp/bitwarden-install.sh /etc/profile.d/bitwarden-install.sh",
"sudo chown root:root /etc/update-motd.d/99-bitwarden-welcome /etc/ufw/applications.d/bitwarden /opt/bitwarden/install-bitwarden.sh /var/lib/cloud/scripts/per-instance/001_onboot /etc/profile.d/bitwarden-install.sh",
"sudo chmod 644 /etc/ufw/applications.d/bitwarden /etc/profile.d/bitwarden-install.sh"
]
}
provisioner "shell" {
environment_vars = [
"DEBIAN_FRONTEND=noninteractive",
"LC_ALL=C",
"LANG=en_US.UTF-8",
"LC_CTYPE=en_US.UTF-8"
]
inline = [
"sudo apt-get -qqy update",
"sudo apt-get -qqy -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' full-upgrade",
"sudo apt-get -qqy -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' install ${var.apt_packages}",
"sudo install -m 0755 -d /etc/apt/keyrings",
"curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg",
"sudo chmod a+r /etc/apt/keyrings/docker.gpg",
"echo \"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo \"$VERSION_CODENAME\") stable\" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null",
"sudo apt-get -qqy update",
"sudo apt-get -qqy -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' install ${var.docker_packages}",
"sudo apt-get -qqy clean"
]
}
provisioner "shell" {
execute_command = "chmod +x {{ .Path }}; {{ .Vars }} sudo -E bash '{{ .Path }}'"
environment_vars = [
"application_name=${var.application_name}",
"application_version=${var.application_version}",
"DEBIAN_FRONTEND=noninteractive",
"LC_ALL=C",
"LANG=en_US.UTF-8",
"LC_CTYPE=en_US.UTF-8"
]
scripts = [
"scripts/01-setup-first-run.sh",
"scripts/02-ufw-bitwarden.sh",
"scripts/90-cleanup.sh",
"scripts/99-img-check.sh"
]
}
post-processor "manifest" {
output = "manifest.json"
strip_path = true
}
}

View File

@@ -0,0 +1,29 @@
#!/bin/bash
#
# Scripts in this directory are run during the build process.
# Each script will be uploaded to /tmp on your build instance,
# given execute permissions and run. The cleanup process will
# remove the scripts from your build system after they have run
# if you use the build_image task.
#
#
# Create dedicated bitwarden user with Docker access
#
useradd -m -s /bin/bash bitwarden
usermod -aG docker bitwarden
#
# Make MOTD and boot script executable
#
chmod +x /var/lib/cloud/scripts/per-instance/001_onboot
chmod +x /etc/update-motd.d/99-bitwarden-welcome
#
# Setup First Run Script
#
chmod +x /opt/bitwarden/install-bitwarden.sh

View File

@@ -0,0 +1,6 @@
#!/bin/sh
ufw allow ssh
ufw allow 'Bitwarden'
ufw --force enable

View File

@@ -0,0 +1,29 @@
#!/bin/bash
# AWS Marketplace Image Cleanup
set -o errexit
# Ensure /tmp exists and has the proper permissions
if [ ! -d /tmp ]; then
mkdir /tmp
fi
chmod 1777 /tmp
if [ -n "$(command -v apt-get)" ]; then
export DEBIAN_FRONTEND=noninteractive
apt-get -y update
apt-get -o Dpkg::Options::="--force-confold" upgrade -q -y
apt-get -y autoremove
apt-get -y autoclean
fi
rm -rf /tmp/* /var/tmp/*
cat /dev/null > /root/.bash_history
unset HISTFILE
find /var/log -mtime -1 -type f -exec truncate -s 0 {} \;
rm -rf /var/log/*.gz /var/log/*.[0-9] /var/log/*-????????
rm -rf /var/lib/cloud/instances/*
rm -f /root/.ssh/authorized_keys /etc/ssh/*key*
touch /etc/ssh/revoked_keys
chmod 600 /etc/ssh/revoked_keys

View File

@@ -0,0 +1,162 @@
#!/bin/bash
# AWS Marketplace Image Validation Tool
VERSION="v. 1.0.0"
RUNDATE=$( date )
# Script should be run with SUDO
if [ "$EUID" -ne 0 ]
then echo "[Error] - This script must be run with sudo or as the root user."
exit 1
fi
STATUS=0
PASS=0
WARN=0
FAIL=0
clear
echo "AWS Marketplace Image Validation Tool ${VERSION}"
echo "Executed on: ${RUNDATE}"
echo "Checking local system for Marketplace compatibility..."
echo ""
# Check OS
if [ -f /etc/os-release ]; then
. /etc/os-release
OS=$NAME
VER=$VERSION_ID
else
OS=$(uname -s)
VER=$(uname -r)
fi
echo -en "Distribution: ${OS}\n"
echo -en "Version: ${VER}\n\n"
if [[ $OS == "Ubuntu" ]] && [[ $VER == "22.04" ]]; then
echo -en "\e[32m[PASS]\e[0m Supported OS detected: ${OS} ${VER}\n"
((PASS++))
else
echo -en "\e[41m[FAIL]\e[0m ${OS} ${VER} is not the expected OS (Ubuntu 22.04)\n"
((FAIL++))
STATUS=2
fi
# Check cloud-init
if hash cloud-init 2>/dev/null; then
echo -en "\e[32m[PASS]\e[0m Cloud-init is installed.\n"
((PASS++))
else
echo -en "\e[41m[FAIL]\e[0m Cloud-init is not installed.\n"
((FAIL++))
STATUS=2
fi
# Check Docker
if hash docker 2>/dev/null; then
echo -en "\e[32m[PASS]\e[0m Docker is installed.\n"
((PASS++))
else
echo -en "\e[41m[FAIL]\e[0m Docker is not installed.\n"
((FAIL++))
STATUS=2
fi
# Check firewall
if [[ $OS == "Ubuntu" ]]; then
ufwa=$(ufw status | head -1 | sed -e "s/^Status:\ //")
if [[ $ufwa == "active" ]]; then
echo -en "\e[32m[PASS]\e[0m Firewall (ufw) is active.\n"
((PASS++))
else
echo -en "\e[93m[WARN]\e[0m Firewall (ufw) is not active.\n"
((WARN++))
if [[ $STATUS != 2 ]]; then STATUS=1; fi
fi
fi
# Check root password
SHADOW=$(cat /etc/shadow)
for usr in $SHADOW; do
IFS=':' read -r -a u <<< "$usr"
if [[ "${u[0]}" == "root" ]]; then
if [[ ${u[1]} == "!" ]] || [[ ${u[1]} == "!!" ]] || [[ ${u[1]} == "*" ]]; then
echo -en "\e[32m[PASS]\e[0m Root user has no password set.\n"
((PASS++))
else
echo -en "\e[41m[FAIL]\e[0m Root user has a password set.\n"
((FAIL++))
STATUS=2
fi
fi
done
# Check SSH keys
if [ -f /root/.ssh/authorized_keys ] && [ "$(wc -c < /root/.ssh/authorized_keys)" -gt 50 ]; then
echo -en "\e[41m[FAIL]\e[0m Root has a populated authorized_keys file.\n"
((FAIL++))
STATUS=2
else
echo -en "\e[32m[PASS]\e[0m No SSH keys found for root.\n"
((PASS++))
fi
# Check bash history
if [ -f /root/.bash_history ]; then
BH_S=$(wc -c < /root/.bash_history)
if [[ $BH_S -lt 200 ]]; then
echo -en "\e[32m[PASS]\e[0m Root bash history appears cleared.\n"
((PASS++))
else
echo -en "\e[41m[FAIL]\e[0m Root bash history should be cleared.\n"
((FAIL++))
STATUS=2
fi
else
echo -en "\e[32m[PASS]\e[0m Root bash history is not present.\n"
((PASS++))
fi
# Check for log files
echo -en "\nChecking for log files in /var/log\n"
for f in /var/log/*-????????; do
[[ -e $f ]] || break
echo -en "\e[93m[WARN]\e[0m Log archive ${f} found.\n"
((WARN++))
if [[ $STATUS != 2 ]]; then STATUS=1; fi
done
for f in /var/log/*.[0-9]; do
[[ -e $f ]] || break
echo -en "\e[93m[WARN]\e[0m Log archive ${f} found.\n"
((WARN++))
if [[ $STATUS != 2 ]]; then STATUS=1; fi
done
# Summary
echo -en "\n---------------------------------------------------------------------------------------------------\n"
if [[ $STATUS == 0 ]]; then
echo -en "Scan Complete.\n\e[32mAll Tests Passed!\e[0m\n"
elif [[ $STATUS == 1 ]]; then
echo -en "Scan Complete.\n\e[93mSome non-critical tests failed. Please review these items.\e[0m\n"
else
echo -en "Scan Complete.\n\e[41mOne or more tests failed. Please review these items and re-test.\e[0m\n"
fi
echo "---------------------------------------------------------------------------------------------------"
echo -en "\e[1m${PASS} Tests PASSED\e[0m\n"
echo -en "\e[1m${WARN} WARNINGS\e[0m\n"
echo -en "\e[1m${FAIL} Tests FAILED\e[0m\n"
echo -en "---------------------------------------------------------------------------------------------------\n"
if [[ $STATUS == 0 ]]; then
echo -en "No issues detected. Ensure all software is functional, secure, and properly configured.\n\n"
exit 0
elif [[ $STATUS == 1 ]]; then
echo -en "Please review all [WARN] items above and ensure they are intended or resolved.\n\n"
exit 0
else
echo -en "Critical tests failed. These must be resolved before submitting to AWS Marketplace.\n\n"
exit 1
fi