mirror of
https://github.com/bitwarden/self-host.git
synced 2026-06-28 14:25:45 +00:00
Bitwarden Lite Marketplace Images (#491)
* lite marketplace listings * reuse ufw
This commit is contained in:
4
.github/CODEOWNERS
vendored
4
.github/CODEOWNERS
vendored
@@ -16,9 +16,13 @@
|
||||
# Bitwarden lite
|
||||
.github/workflows/build-bitwarden-lite.yml @bitwarden/dept-shot
|
||||
bitwarden-lite/ @bitwarden/dept-shot
|
||||
CommonMarketplaceLite/ @bitwarden/dept-shot
|
||||
|
||||
# Release workflows
|
||||
.github/workflows/release-aws.yml @bitwarden/dept-bre
|
||||
.github/workflows/release-aws-lite.yml @bitwarden/dept-bre
|
||||
.github/workflows/release-azure.yml @bitwarden/dept-bre
|
||||
.github/workflows/release-azure-lite.yml @bitwarden/dept-bre
|
||||
.github/workflows/release-digital-ocean.yml @bitwarden/dept-bre
|
||||
.github/workflows/release-digital-ocean-lite.yml @bitwarden/dept-bre
|
||||
.github/workflows/release.yml @bitwarden/dept-bre
|
||||
|
||||
182
.github/workflows/release-aws-lite.yml
vendored
Normal file
182
.github/workflows/release-aws-lite.yml
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
name: Release AWS Marketplace - Bitwarden Lite
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "AWSMarketplace/marketplace-image-lite.pkr.hcl"
|
||||
- "AWSMarketplace/scripts/99-img-check-lite.sh"
|
||||
- "CommonMarketplaceLite/**"
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_version:
|
||||
description: "Release version (e.g., 2026.3.2)"
|
||||
required: false
|
||||
type: string
|
||||
|
||||
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
|
||||
id: set-version
|
||||
env:
|
||||
RELEASE_VERSION: ${{ inputs.release_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"
|
||||
|
||||
# Use the release version input when dispatched from the release
|
||||
# pipeline. Fall back to coreVersion for other builds.
|
||||
if [[ -n "$RELEASE_VERSION" ]]; then
|
||||
echo "img_version=$RELEASE_VERSION" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "img_version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Set up Hashicorp Packer
|
||||
uses: hashicorp/setup-packer@1aa358be5cf73883762b302a3a03abd66e75b232 # v3.1.0
|
||||
|
||||
- name: Build AWS Lite 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.img_version }}
|
||||
working-directory: ./AWSMarketplace
|
||||
run: |
|
||||
packer version
|
||||
packer init -upgrade marketplace-image-lite.pkr.hcl
|
||||
packer build marketplace-image-lite.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 Lite 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-lite.json ]; then
|
||||
AMI_ID=$(jq -r '.builds[-1].artifact_id' manifest-lite.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 Lite Image Cleanup
|
||||
working-directory: ./AWSMarketplace
|
||||
if: ${{ inputs.release_version == '' }}
|
||||
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-lite.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
|
||||
180
.github/workflows/release-azure-lite.yml
vendored
Normal file
180
.github/workflows/release-azure-lite.yml
vendored
Normal file
@@ -0,0 +1,180 @@
|
||||
name: Release Azure Marketplace - Bitwarden Lite
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "AzureMarketplace/marketplace-image-lite.pkr.hcl"
|
||||
- "AzureMarketplace/scripts/99-img-check-lite.sh"
|
||||
- "CommonMarketplaceLite/**"
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_version:
|
||||
description: "Release version (e.g., 2026.3.2)"
|
||||
required: false
|
||||
type: string
|
||||
|
||||
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: "azure-marketplace-subscription-id"
|
||||
|
||||
- name: Set version
|
||||
id: set-version
|
||||
env:
|
||||
RELEASE_VERSION: ${{ inputs.release_version }}
|
||||
RUN_NUMBER: ${{ github.run_number }}
|
||||
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"
|
||||
|
||||
# Use the release version input when dispatched from the release
|
||||
# pipeline. Fall back to a dev version for push builds to avoid
|
||||
# colliding with published gallery image versions.
|
||||
if [[ -n "$RELEASE_VERSION" ]]; then
|
||||
echo "img_version=$RELEASE_VERSION" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "img_version=0.0.$RUN_NUMBER" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Set up Hashicorp Packer
|
||||
uses: hashicorp/setup-packer@1aa358be5cf73883762b302a3a03abd66e75b232 # v3.1.0
|
||||
|
||||
- name: Build Azure Lite Image
|
||||
env:
|
||||
AZURE_SUBSCRIPTION_ID: ${{ steps.retrieve-secrets.outputs.azure-marketplace-subscription-id }}
|
||||
AZURE_RESOURCE_GROUP: rg-marketplace
|
||||
AZURE_GALLERY_NAME: bitwarden_marketplace
|
||||
AZURE_GALLERY_IMAGE_NAME: "bitwarden-lite-self-host"
|
||||
AZURE_IMG_VERSION: ${{ steps.set-version.outputs.img_version }}
|
||||
working-directory: ./AzureMarketplace
|
||||
run: |
|
||||
packer version
|
||||
packer init -upgrade marketplace-image-lite.pkr.hcl
|
||||
packer build marketplace-image-lite.pkr.hcl
|
||||
|
||||
- name: Cleanup orphaned resources on cancellation or failure
|
||||
if: cancelled() || failure()
|
||||
env:
|
||||
RESOURCE_GROUP: rg-marketplace
|
||||
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 resources with tag: \`github-run-${{ github.run_id }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Find and delete VMs tagged with this run
|
||||
VM_IDS=$(az vm list \
|
||||
--resource-group "$RESOURCE_GROUP" \
|
||||
--query "[?tags.github_run=='github-run-${{ github.run_id }}'].id" \
|
||||
--output tsv)
|
||||
|
||||
if [ -n "$VM_IDS" ]; then
|
||||
echo "Found orphaned VMs, deleting..."
|
||||
echo "### Orphaned VMs Found and Deleted" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
for VM_ID in $VM_IDS; do
|
||||
echo "Deleting VM: $VM_ID"
|
||||
if az vm delete --ids "$VM_ID" --yes --force-deletion true; then
|
||||
echo "| $VM_ID | :white_check_mark: Deleted |" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "| $VM_ID | :x: Failed to delete |" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "No orphaned VMs 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: ./AzureMarketplace
|
||||
run: |
|
||||
echo "## :rocket: Azure Marketplace Lite 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-lite.json ]; then
|
||||
IMAGE_ID=$(jq -r '.builds[-1].artifact_id' manifest-lite.json)
|
||||
echo "**Image ID:** \`$IMAGE_ID\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
echo ":white_check_mark: Packer build VM was automatically cleaned up" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Azure Lite Image Cleanup
|
||||
working-directory: ./AzureMarketplace
|
||||
if: ${{ inputs.release_version == '' }}
|
||||
env:
|
||||
RESOURCE_GROUP: rg-marketplace
|
||||
GALLERY_NAME: bitwarden_marketplace
|
||||
VERSION: ${{ steps.set-version.outputs.img_version }}
|
||||
run: |
|
||||
# Get the managed image name from manifest
|
||||
IMAGE_NAME=$(jq -r '.builds[-1].custom_data.managed_image_name // empty' manifest-lite.json)
|
||||
|
||||
# Delete the gallery image version
|
||||
az sig image-version delete \
|
||||
--resource-group "$RESOURCE_GROUP" \
|
||||
--gallery-name "$GALLERY_NAME" \
|
||||
--gallery-image-definition "bitwarden-lite-self-host" \
|
||||
--gallery-image-version "$VERSION" \
|
||||
2>/dev/null || true
|
||||
|
||||
# Delete the managed image
|
||||
if [ -n "$IMAGE_NAME" ]; then
|
||||
az image delete \
|
||||
--resource-group "$RESOURCE_GROUP" \
|
||||
--name "$IMAGE_NAME" \
|
||||
2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Update summary for non-release builds
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "---" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo ":wastebasket: **Non-release build:** Image was automatically cleaned up" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Log out from Azure
|
||||
if: always()
|
||||
uses: bitwarden/gh-actions/azure-logout@main
|
||||
169
.github/workflows/release-digital-ocean-lite.yml
vendored
Normal file
169
.github/workflows/release-digital-ocean-lite.yml
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
name: Release Digital Ocean 1-Click - Bitwarden Lite
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "DigitalOceanMarketplace/marketplace-image-lite.pkr.hcl"
|
||||
- "DigitalOceanMarketplace/scripts/99-img-check-lite.sh"
|
||||
- "CommonMarketplaceLite/**"
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_version:
|
||||
description: "Release version (e.g., 2026.3.2)"
|
||||
required: false
|
||||
type: string
|
||||
|
||||
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: "bitwarden-ci"
|
||||
secrets: "digital-ocean-api-key"
|
||||
|
||||
- name: Log out from Azure
|
||||
uses: bitwarden/gh-actions/azure-logout@main
|
||||
|
||||
- name: Set version
|
||||
id: set-version
|
||||
env:
|
||||
RELEASE_VERSION: ${{ inputs.release_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"
|
||||
|
||||
# Use the release version input when dispatched from the release
|
||||
# pipeline. Fall back to coreVersion for other builds.
|
||||
if [[ -n "$RELEASE_VERSION" ]]; then
|
||||
echo "img_version=$RELEASE_VERSION" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "img_version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Set up Hashicorp Packer
|
||||
uses: hashicorp/setup-packer@1aa358be5cf73883762b302a3a03abd66e75b232 # v3.1.0
|
||||
|
||||
- name: Build Digital Ocean Lite Image
|
||||
env:
|
||||
DIGITALOCEAN_TOKEN: ${{ steps.retrieve-secrets.outputs.digital-ocean-api-key }}
|
||||
DIGITALOCEAN_IMG_VERSION: ${{ steps.set-version.outputs.img_version }}
|
||||
working-directory: ./DigitalOceanMarketplace
|
||||
run: |
|
||||
packer version
|
||||
packer init -upgrade marketplace-image-lite.pkr.hcl
|
||||
packer build marketplace-image-lite.pkr.hcl
|
||||
|
||||
- name: Install doctl
|
||||
uses: digitalocean/action-doctl@135ac0aa0eed4437d547c6f12c364d3006b42824 # v2.5.1
|
||||
with:
|
||||
token: ${{ steps.retrieve-secrets.outputs.digital-ocean-api-key }}
|
||||
|
||||
- name: Cleanup orphaned droplets on cancellation
|
||||
if: cancelled()
|
||||
run: |
|
||||
echo "Workflow cancelled - cleaning up any orphaned droplets..."
|
||||
|
||||
# Write to workflow summary
|
||||
echo "## :warning: Workflow Cancelled - Droplet Cleanup" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Searching for orphaned droplets with tag: \`github-run-${{ github.run_id }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Find droplets with our workflow run tag
|
||||
DROPLET_IDS=$(doctl compute droplet list \
|
||||
--tag-name "github-run-${{ github.run_id }}" \
|
||||
--format ID \
|
||||
--no-header)
|
||||
|
||||
if [ -n "$DROPLET_IDS" ]; then
|
||||
echo "Found orphaned droplets: $DROPLET_IDS"
|
||||
echo "### Orphaned Droplets Found and Deleted" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Droplet ID | Status |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|------------|--------|" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
for DROPLET_ID in $DROPLET_IDS; do
|
||||
echo "Deleting droplet $DROPLET_ID..."
|
||||
if doctl compute droplet delete "$DROPLET_ID" --force; then
|
||||
echo "| $DROPLET_ID | :white_check_mark: Deleted |" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "| $DROPLET_ID | :x: Failed to delete |" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
done
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo ":white_check_mark: Cleanup complete" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "No orphaned droplets found"
|
||||
echo ":white_check_mark: No orphaned droplets found - nothing to clean up" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
- name: Add build summary
|
||||
if: success()
|
||||
env:
|
||||
VERSION: ${{ steps.set-version.outputs.version }}
|
||||
working-directory: ./DigitalOceanMarketplace
|
||||
run: |
|
||||
echo "## :rocket: Digital Ocean Lite 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-lite.json ]; then
|
||||
SNAPSHOT_ID=$(jq -r '.builds[-1].artifact_id' manifest-lite.json | cut -d ":" -f2)
|
||||
SNAPSHOT_NAME=$(jq -r '.builds[-1].custom_data.snapshot_name // "N/A"' manifest-lite.json)
|
||||
echo "**Snapshot ID:** \`$SNAPSHOT_ID\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Snapshot Name:** \`$SNAPSHOT_NAME\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
echo ":white_check_mark: Packer build droplet was automatically cleaned up" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Digital Ocean Lite Image Cleanup
|
||||
working-directory: ./DigitalOceanMarketplace
|
||||
if: ${{ inputs.release_version == '' }}
|
||||
run: |
|
||||
# Get the ID from the snapshot build.
|
||||
DO_ARTIFACT=$(jq -r '.builds[-1].artifact_id' manifest-lite.json | cut -d ":" -f2)
|
||||
|
||||
# Force remove the snapshot
|
||||
doctl compute image delete "$DO_ARTIFACT" -f
|
||||
|
||||
# Update summary for non-release builds
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "---" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo ":wastebasket: **Non-release build:** Snapshot \`$DO_ARTIFACT\` was automatically cleaned up" >> $GITHUB_STEP_SUMMARY
|
||||
48
.github/workflows/release.yml
vendored
48
.github/workflows/release.yml
vendored
@@ -538,3 +538,51 @@ jobs:
|
||||
ref: process.env.RELEASE_TAG,
|
||||
inputs: { release_version: process.env.RELEASE_VERSION }
|
||||
});
|
||||
|
||||
- name: Trigger release-digital-ocean-lite workflow
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
env:
|
||||
RELEASE_TAG: v${{ inputs.release_version }}
|
||||
RELEASE_VERSION: ${{ 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-digital-ocean-lite.yml',
|
||||
ref: process.env.RELEASE_TAG,
|
||||
inputs: { release_version: process.env.RELEASE_VERSION }
|
||||
});
|
||||
|
||||
- name: Trigger release-aws-lite workflow
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
env:
|
||||
RELEASE_TAG: v${{ inputs.release_version }}
|
||||
RELEASE_VERSION: ${{ 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-lite.yml',
|
||||
ref: process.env.RELEASE_TAG,
|
||||
inputs: { release_version: process.env.RELEASE_VERSION }
|
||||
});
|
||||
|
||||
- name: Trigger release-azure-lite workflow
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
env:
|
||||
RELEASE_TAG: v${{ inputs.release_version }}
|
||||
RELEASE_VERSION: ${{ 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-azure-lite.yml',
|
||||
ref: process.env.RELEASE_TAG,
|
||||
inputs: { release_version: process.env.RELEASE_VERSION }
|
||||
});
|
||||
|
||||
164
AWSMarketplace/marketplace-image-lite.pkr.hcl
Normal file
164
AWSMarketplace/marketplace-image-lite.pkr.hcl
Normal file
@@ -0,0 +1,164 @@
|
||||
packer {
|
||||
required_plugins {
|
||||
amazon = {
|
||||
version = ">= 1.2.0"
|
||||
source = "github.com/hashicorp/amazon"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "application_name" {
|
||||
type = string
|
||||
default = "Bitwarden Lite"
|
||||
}
|
||||
|
||||
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-lite-24-04-${local.timestamp}"
|
||||
}
|
||||
|
||||
source "amazon-ebs" "bitwarden_lite" {
|
||||
region = var.aws_region
|
||||
instance_type = "t3.small"
|
||||
ssh_username = "ubuntu"
|
||||
|
||||
source_ami_filter {
|
||||
filters = {
|
||||
name = "ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"
|
||||
root-device-type = "ebs"
|
||||
virtualization-type = "hvm"
|
||||
}
|
||||
most_recent = true
|
||||
owners = ["099720109477"] # Canonical
|
||||
}
|
||||
|
||||
launch_block_device_mappings {
|
||||
device_name = "/dev/sda1"
|
||||
volume_size = 32
|
||||
volume_type = "gp3"
|
||||
delete_on_termination = true
|
||||
}
|
||||
|
||||
ami_name = local.image_name
|
||||
ami_description = "Bitwarden Lite 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-lite-${var.github_run_id}"
|
||||
Application = "bitwarden-packer-build"
|
||||
GitHub_Run = "github-run-${var.github_run_id}"
|
||||
}
|
||||
}
|
||||
|
||||
build {
|
||||
sources = ["source.amazon-ebs.bitwarden_lite"]
|
||||
|
||||
provisioner "shell" {
|
||||
inline = ["cloud-init status --wait"]
|
||||
}
|
||||
|
||||
# Upload Bitwarden Lite files to /tmp staging area (amazon-ebs connects as a non-root user)
|
||||
provisioner "file" {
|
||||
source = "../CommonMarketplaceLite/files/etc/update-motd.d/99-bitwarden-welcome"
|
||||
destination = "/tmp/99-bitwarden-welcome"
|
||||
}
|
||||
|
||||
provisioner "file" {
|
||||
source = "../CommonMarketplace/files/etc/ufw/applications.d/bitwarden"
|
||||
destination = "/tmp/bitwarden-ufw"
|
||||
}
|
||||
|
||||
provisioner "file" {
|
||||
source = "../CommonMarketplaceLite/files/var/lib/cloud/scripts/per-instance/001_onboot"
|
||||
destination = "/tmp/001_onboot"
|
||||
}
|
||||
|
||||
# Move staged files to their final system locations
|
||||
provisioner "shell" {
|
||||
inline = [
|
||||
"sudo mkdir -p /etc/update-motd.d /etc/ufw/applications.d /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/001_onboot /var/lib/cloud/scripts/per-instance/001_onboot",
|
||||
"sudo chown root:root /etc/update-motd.d/99-bitwarden-welcome /etc/ufw/applications.d/bitwarden /var/lib/cloud/scripts/per-instance/001_onboot",
|
||||
"sudo chmod 644 /etc/ufw/applications.d/bitwarden"
|
||||
]
|
||||
}
|
||||
|
||||
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 = [
|
||||
"../CommonMarketplace/scripts/01-setup-first-run.sh",
|
||||
"../CommonMarketplace/scripts/02-ufw-bitwarden.sh",
|
||||
"../CommonMarketplace/scripts/90-cleanup.sh",
|
||||
"scripts/99-img-check-lite.sh"
|
||||
]
|
||||
}
|
||||
|
||||
post-processor "manifest" {
|
||||
output = "manifest-lite.json"
|
||||
strip_path = true
|
||||
}
|
||||
}
|
||||
191
AWSMarketplace/scripts/99-img-check-lite.sh
Normal file
191
AWSMarketplace/scripts/99-img-check-lite.sh
Normal file
@@ -0,0 +1,191 @@
|
||||
#!/bin/bash
|
||||
|
||||
# AWS Marketplace Image Validation Tool - Bitwarden Lite
|
||||
|
||||
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 (Bitwarden Lite) ${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 == "24.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 24.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 docker compose plugin
|
||||
if docker compose version > /dev/null 2>&1; then
|
||||
echo -en "\e[32m[PASS]\e[0m Docker Compose plugin is installed.\n"
|
||||
((PASS++))
|
||||
else
|
||||
echo -en "\e[41m[FAIL]\e[0m Docker Compose plugin 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
|
||||
|
||||
if [ -f /home/ubuntu/.ssh/authorized_keys ] && [ "$(wc -c < /home/ubuntu/.ssh/authorized_keys)" -gt 50 ]; then
|
||||
echo -en "\e[41m[FAIL]\e[0m Ubuntu user has a populated authorized_keys file.\n"
|
||||
((FAIL++))
|
||||
STATUS=2
|
||||
else
|
||||
echo -en "\e[32m[PASS]\e[0m No SSH keys found for ubuntu user.\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 cloud-init first-boot script is present and executable
|
||||
if [ -x /var/lib/cloud/scripts/per-instance/001_onboot ]; then
|
||||
echo -en "\e[32m[PASS]\e[0m Cloud-init first-boot script is present and executable.\n"
|
||||
((PASS++))
|
||||
else
|
||||
echo -en "\e[41m[FAIL]\e[0m Cloud-init first-boot script not found at /var/lib/cloud/scripts/per-instance/001_onboot.\n"
|
||||
((FAIL++))
|
||||
STATUS=2
|
||||
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
|
||||
191
AzureMarketplace/marketplace-image-lite.pkr.hcl
Normal file
191
AzureMarketplace/marketplace-image-lite.pkr.hcl
Normal file
@@ -0,0 +1,191 @@
|
||||
packer {
|
||||
required_plugins {
|
||||
azure = {
|
||||
version = ">= 2.0.0"
|
||||
source = "github.com/hashicorp/azure"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "application_name" {
|
||||
type = string
|
||||
default = "Bitwarden Lite"
|
||||
}
|
||||
|
||||
variable "application_version" {
|
||||
type = string
|
||||
default = "${env("AZURE_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 "subscription_id" {
|
||||
type = string
|
||||
default = "${env("AZURE_SUBSCRIPTION_ID")}"
|
||||
}
|
||||
|
||||
variable "resource_group" {
|
||||
type = string
|
||||
default = "${env("AZURE_RESOURCE_GROUP")}"
|
||||
}
|
||||
|
||||
variable "gallery_name" {
|
||||
type = string
|
||||
default = "${env("AZURE_GALLERY_NAME")}"
|
||||
}
|
||||
|
||||
variable "gallery_image_name" {
|
||||
type = string
|
||||
default = "${env("AZURE_GALLERY_IMAGE_NAME")}"
|
||||
}
|
||||
|
||||
variable "location" {
|
||||
type = string
|
||||
default = "East US"
|
||||
}
|
||||
|
||||
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-lite-24-04-${local.timestamp}"
|
||||
}
|
||||
|
||||
source "azure-arm" "bitwarden_lite" {
|
||||
use_azure_cli_auth = true
|
||||
subscription_id = var.subscription_id
|
||||
|
||||
os_type = "Linux"
|
||||
image_publisher = "Canonical"
|
||||
image_offer = "ubuntu-24_04-lts"
|
||||
image_sku = "server"
|
||||
|
||||
build_resource_group_name = var.resource_group
|
||||
vm_size = "Standard_B2s"
|
||||
|
||||
managed_image_name = local.image_name
|
||||
managed_image_resource_group_name = var.resource_group
|
||||
|
||||
shared_image_gallery_destination {
|
||||
subscription = var.subscription_id
|
||||
resource_group = var.resource_group
|
||||
gallery_name = var.gallery_name
|
||||
image_name = var.gallery_image_name
|
||||
image_version = var.application_version
|
||||
replication_regions = [var.location]
|
||||
}
|
||||
|
||||
azure_tags = {
|
||||
application = "bitwarden-packer-build"
|
||||
github_run = "github-run-${var.github_run_id}"
|
||||
}
|
||||
}
|
||||
|
||||
build {
|
||||
sources = ["source.azure-arm.bitwarden_lite"]
|
||||
|
||||
provisioner "shell" {
|
||||
inline = ["cloud-init status --wait"]
|
||||
}
|
||||
|
||||
# Upload Bitwarden Lite files to /tmp staging area (azure-arm connects as a non-root user)
|
||||
provisioner "file" {
|
||||
source = "../CommonMarketplaceLite/files/etc/update-motd.d/99-bitwarden-welcome"
|
||||
destination = "/tmp/99-bitwarden-welcome"
|
||||
}
|
||||
|
||||
provisioner "file" {
|
||||
source = "../CommonMarketplace/files/etc/ufw/applications.d/bitwarden"
|
||||
destination = "/tmp/bitwarden-ufw"
|
||||
}
|
||||
|
||||
provisioner "file" {
|
||||
source = "../CommonMarketplaceLite/files/var/lib/cloud/scripts/per-instance/001_onboot"
|
||||
destination = "/tmp/001_onboot"
|
||||
}
|
||||
|
||||
# Move staged files to their final system locations
|
||||
provisioner "shell" {
|
||||
inline = [
|
||||
"sudo mkdir -p /etc/update-motd.d /etc/ufw/applications.d /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/001_onboot /var/lib/cloud/scripts/per-instance/001_onboot",
|
||||
"sudo chown root:root /etc/update-motd.d/99-bitwarden-welcome /etc/ufw/applications.d/bitwarden /var/lib/cloud/scripts/per-instance/001_onboot",
|
||||
"sudo chmod 644 /etc/ufw/applications.d/bitwarden"
|
||||
]
|
||||
}
|
||||
|
||||
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 = [
|
||||
"../CommonMarketplace/scripts/01-setup-first-run.sh",
|
||||
"../CommonMarketplace/scripts/02-ufw-bitwarden.sh",
|
||||
"../CommonMarketplace/scripts/90-cleanup.sh",
|
||||
"scripts/99-img-check-lite.sh"
|
||||
]
|
||||
}
|
||||
|
||||
# Azure-specific cleanup
|
||||
provisioner "shell" {
|
||||
execute_command = "chmod +x {{ .Path }}; {{ .Vars }} sudo -E bash '{{ .Path }}'"
|
||||
inline = [
|
||||
"truncate -s 0 /var/log/waagent.log 2>/dev/null || true"
|
||||
]
|
||||
}
|
||||
|
||||
# Azure generalization - must be the last provisioner
|
||||
provisioner "shell" {
|
||||
execute_command = "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'"
|
||||
inline = [
|
||||
"/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync"
|
||||
]
|
||||
}
|
||||
|
||||
post-processor "manifest" {
|
||||
output = "manifest-lite.json"
|
||||
strip_path = true
|
||||
}
|
||||
}
|
||||
201
AzureMarketplace/scripts/99-img-check-lite.sh
Normal file
201
AzureMarketplace/scripts/99-img-check-lite.sh
Normal file
@@ -0,0 +1,201 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Azure Marketplace Image Validation Tool - Bitwarden Lite
|
||||
|
||||
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 "Azure Marketplace Image Validation Tool (Bitwarden Lite) ${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 == "24.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 24.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 Azure Linux Agent
|
||||
if hash waagent 2>/dev/null; then
|
||||
echo -en "\e[32m[PASS]\e[0m Azure Linux Agent (waagent) is installed.\n"
|
||||
((PASS++))
|
||||
else
|
||||
echo -en "\e[41m[FAIL]\e[0m Azure Linux Agent (waagent) 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 docker compose plugin
|
||||
if docker compose version > /dev/null 2>&1; then
|
||||
echo -en "\e[32m[PASS]\e[0m Docker Compose plugin is installed.\n"
|
||||
((PASS++))
|
||||
else
|
||||
echo -en "\e[41m[FAIL]\e[0m Docker Compose plugin 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
|
||||
|
||||
if [ -f /home/ubuntu/.ssh/authorized_keys ] && [ "$(wc -c < /home/ubuntu/.ssh/authorized_keys)" -gt 50 ]; then
|
||||
echo -en "\e[41m[FAIL]\e[0m Ubuntu user has a populated authorized_keys file.\n"
|
||||
((FAIL++))
|
||||
STATUS=2
|
||||
else
|
||||
echo -en "\e[32m[PASS]\e[0m No SSH keys found for ubuntu user.\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 cloud-init first-boot script is present and executable
|
||||
if [ -x /var/lib/cloud/scripts/per-instance/001_onboot ]; then
|
||||
echo -en "\e[32m[PASS]\e[0m Cloud-init first-boot script is present and executable.\n"
|
||||
((PASS++))
|
||||
else
|
||||
echo -en "\e[41m[FAIL]\e[0m Cloud-init first-boot script not found at /var/lib/cloud/scripts/per-instance/001_onboot.\n"
|
||||
((FAIL++))
|
||||
STATUS=2
|
||||
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 Azure Marketplace.\n\n"
|
||||
exit 1
|
||||
fi
|
||||
@@ -26,4 +26,6 @@ chmod +x /etc/update-motd.d/99-bitwarden-welcome
|
||||
# Setup First Run Script
|
||||
#
|
||||
|
||||
chmod +x /opt/bitwarden/install-bitwarden.sh
|
||||
if [ -f /opt/bitwarden/install-bitwarden.sh ]; then
|
||||
chmod +x /opt/bitwarden/install-bitwarden.sh
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Configured as part of the Bitwarden Lite Marketplace Image build process
|
||||
|
||||
SETTINGS_FILE="/home/bitwarden/settings.env"
|
||||
|
||||
# Read current values from settings.env if it exists
|
||||
if [ -f "$SETTINGS_FILE" ]; then
|
||||
BW_DOMAIN=$(grep "^BW_DOMAIN=" "$SETTINGS_FILE" | cut -d'=' -f2-)
|
||||
BW_INSTALLATION_ID=$(grep "^BW_INSTALLATION_ID=" "$SETTINGS_FILE" | cut -d'=' -f2-)
|
||||
BW_INSTALLATION_KEY=$(grep "^BW_INSTALLATION_KEY=" "$SETTINGS_FILE" | cut -d'=' -f2-)
|
||||
else
|
||||
BW_DOMAIN="(settings.env not found)"
|
||||
BW_INSTALLATION_ID=""
|
||||
BW_INSTALLATION_KEY=""
|
||||
fi
|
||||
|
||||
# Check which required fields still need to be configured
|
||||
NEEDS_CONFIG=0
|
||||
CONFIG_ITEMS=""
|
||||
|
||||
if [ "$BW_DOMAIN" = "bitwarden.example.com" ] || [ -z "$BW_DOMAIN" ]; then
|
||||
CONFIG_ITEMS="${CONFIG_ITEMS} - BW_DOMAIN is not set (currently: ${BW_DOMAIN})\n"
|
||||
NEEDS_CONFIG=1
|
||||
fi
|
||||
|
||||
if [ "$BW_INSTALLATION_ID" = "00000000-0000-0000-0000-000000000000" ] || [ -z "$BW_INSTALLATION_ID" ]; then
|
||||
CONFIG_ITEMS="${CONFIG_ITEMS} - BW_INSTALLATION_ID is not set\n"
|
||||
NEEDS_CONFIG=1
|
||||
fi
|
||||
|
||||
if [ "$BW_INSTALLATION_KEY" = "xxxxxxxxxxxx" ] || [ -z "$BW_INSTALLATION_KEY" ]; then
|
||||
CONFIG_ITEMS="${CONFIG_ITEMS} - BW_INSTALLATION_KEY is not set\n"
|
||||
NEEDS_CONFIG=1
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
********************************************************************************
|
||||
|
||||
Welcome to your Bitwarden Lite server
|
||||
https://bitwarden.com
|
||||
|
||||
Documentation:
|
||||
https://bitwarden.com/help/install-and-deploy-lite/
|
||||
|
||||
Current configuration ($SETTINGS_FILE):
|
||||
Domain: ${BW_DOMAIN}
|
||||
Installation ID: ${BW_INSTALLATION_ID}
|
||||
|
||||
EOF
|
||||
|
||||
if [ "$NEEDS_CONFIG" -eq 1 ]; then
|
||||
printf "Action required - the following settings still need to be configured:\n"
|
||||
printf "${CONFIG_ITEMS}"
|
||||
printf "\n Get your Installation ID and Key at: https://bitwarden.com/host/\n"
|
||||
printf "\n After editing settings.env, restart Bitwarden Lite:\n"
|
||||
printf " cd /home/bitwarden && docker compose up -d\n"
|
||||
else
|
||||
printf " Bitwarden Lite is configured. Access your vault at https://${BW_DOMAIN}\n"
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
|
||||
Common commands:
|
||||
sudo -i -u bitwarden docker compose -f /home/bitwarden/docker-compose.yml stop
|
||||
sudo -i -u bitwarden docker compose -f /home/bitwarden/docker-compose.yml restart
|
||||
sudo -i -u bitwarden docker compose -f /home/bitwarden/docker-compose.yml pull && \\
|
||||
sudo -i -u bitwarden docker compose -f /home/bitwarden/docker-compose.yml up -d
|
||||
|
||||
********************************************************************************
|
||||
To delete this message of the day: rm -rf $(readlink -f ${0})
|
||||
EOF
|
||||
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Scripts in this directory will be executed by cloud-init on the first boot of servers
|
||||
# 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 Lite
|
||||
# ref: https://bitwarden.com/help/install-and-deploy-lite/
|
||||
#
|
||||
|
||||
# Generate a random database password
|
||||
DB_PASSWORD=$(openssl rand -hex 16)
|
||||
|
||||
# Download docker-compose.yml
|
||||
curl -L -s -o /home/bitwarden/docker-compose.yml \
|
||||
"https://raw.githubusercontent.com/bitwarden/self-host/main/bitwarden-lite/docker-compose.yml"
|
||||
|
||||
# Download settings.env and set the generated database password
|
||||
curl -L -s -o /home/bitwarden/settings.env \
|
||||
"https://raw.githubusercontent.com/bitwarden/self-host/main/bitwarden-lite/settings.env"
|
||||
|
||||
sed -i "s|^BW_DB_PASSWORD=.*|BW_DB_PASSWORD=${DB_PASSWORD}|" /home/bitwarden/settings.env
|
||||
|
||||
# Update the MariaDB container password in docker-compose.yml to match
|
||||
sed -i "s|MARIADB_PASSWORD: \"super_strong_password\"|MARIADB_PASSWORD: \"${DB_PASSWORD}\"|" \
|
||||
/home/bitwarden/docker-compose.yml
|
||||
|
||||
chmod 600 /home/bitwarden/settings.env
|
||||
chown bitwarden:bitwarden /home/bitwarden/docker-compose.yml /home/bitwarden/settings.env
|
||||
|
||||
# Start Bitwarden Lite with default configuration.
|
||||
# Database migrations run automatically on first boot.
|
||||
# Edit /home/bitwarden/settings.env to set your domain and installation keys,
|
||||
# then restart with: cd /home/bitwarden && docker compose up -d
|
||||
cd /home/bitwarden && docker compose up -d
|
||||
|
||||
#
|
||||
# Setup Bitwarden Lite update cron
|
||||
# ref: https://bitwarden.com/help/install-and-deploy-lite/
|
||||
#
|
||||
|
||||
printf '#!/usr/bin/env bash\ncd /home/bitwarden && docker compose pull && docker compose up -d\n' \
|
||||
> /etc/cron.weekly/bitwardenupdate
|
||||
chmod +x /etc/cron.weekly/bitwardenupdate
|
||||
139
DigitalOceanMarketplace/marketplace-image-lite.pkr.hcl
Normal file
139
DigitalOceanMarketplace/marketplace-image-lite.pkr.hcl
Normal file
@@ -0,0 +1,139 @@
|
||||
packer {
|
||||
required_plugins {
|
||||
digitalocean = {
|
||||
version = ">= 1.0.4"
|
||||
source = "github.com/digitalocean/digitalocean"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "application_name" {
|
||||
type = string
|
||||
default = "Bitwarden Lite"
|
||||
}
|
||||
|
||||
variable "application_version" {
|
||||
type = string
|
||||
default = "${env("DIGITALOCEAN_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 "do_token" {
|
||||
type = string
|
||||
default = "${env("DIGITALOCEAN_TOKEN")}"
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
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-lite-24-04-snapshot-${local.timestamp}"
|
||||
}
|
||||
|
||||
source "digitalocean" "bitwarden_lite" {
|
||||
api_token = "${var.do_token}"
|
||||
image = "ubuntu-24-04-x64"
|
||||
region = "nyc3"
|
||||
size = "s-1vcpu-2gb"
|
||||
snapshot_name = "${local.image_name}"
|
||||
ssh_username = "root"
|
||||
tags = [
|
||||
"bitwarden-packer-build",
|
||||
"github-run-${var.github_run_id}"
|
||||
]
|
||||
}
|
||||
|
||||
build {
|
||||
sources = ["source.digitalocean.bitwarden_lite"]
|
||||
|
||||
provisioner "shell" {
|
||||
inline = ["cloud-init status --wait"]
|
||||
}
|
||||
|
||||
# Upload Bitwarden Lite files directly (DO connects as root)
|
||||
provisioner "file" {
|
||||
destination = "/etc/update-motd.d/99-bitwarden-welcome"
|
||||
source = "../CommonMarketplaceLite/files/etc/update-motd.d/99-bitwarden-welcome"
|
||||
}
|
||||
|
||||
provisioner "file" {
|
||||
destination = "/etc/ufw/applications.d/bitwarden"
|
||||
source = "../CommonMarketplace/files/etc/ufw/applications.d/bitwarden"
|
||||
}
|
||||
|
||||
provisioner "file" {
|
||||
destination = "/var/lib/cloud/scripts/per-instance/001_onboot"
|
||||
source = "../CommonMarketplaceLite/files/var/lib/cloud/scripts/per-instance/001_onboot"
|
||||
}
|
||||
|
||||
provisioner "shell" {
|
||||
environment_vars = [
|
||||
"DEBIAN_FRONTEND=noninteractive",
|
||||
"LC_ALL=C",
|
||||
"LANG=en_US.UTF-8",
|
||||
"LC_CTYPE=en_US.UTF-8"
|
||||
]
|
||||
inline = [
|
||||
"apt-get -qqy update",
|
||||
"apt-get -qqy -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' full-upgrade",
|
||||
"apt-get -qqy -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' install ${var.apt_packages}",
|
||||
"install -m 0755 -d /etc/apt/keyrings",
|
||||
"curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg",
|
||||
"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\" | tee /etc/apt/sources.list.d/docker.list > /dev/null",
|
||||
"apt-get -qqy update",
|
||||
"apt-get -qqy -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' install ${var.docker_packages}",
|
||||
"apt-get -qqy clean",
|
||||
"rm -rf /opt/digitalocean",
|
||||
"rm -rf /var/log/auth.log",
|
||||
"rm -rf /var/log/kern.log",
|
||||
"rm -rf /var/log/ufw.log",
|
||||
"rm -rf /var/log/ubuntu-advantage.log",
|
||||
"rm -rf /var/log/droplet-agent.update.log"
|
||||
]
|
||||
}
|
||||
|
||||
provisioner "shell" {
|
||||
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 = [
|
||||
"../CommonMarketplace/scripts/01-setup-first-run.sh",
|
||||
"../CommonMarketplace/scripts/02-ufw-bitwarden.sh",
|
||||
"../CommonMarketplace/scripts/90-cleanup.sh",
|
||||
"scripts/99-img-check-lite.sh"
|
||||
]
|
||||
}
|
||||
|
||||
# DO-specific: securely erase unused disk space for snapshot compression
|
||||
provisioner "shell" {
|
||||
inline = [
|
||||
"dd if=/dev/zero of=/zerofile bs=4096 || rm /zerofile"
|
||||
]
|
||||
}
|
||||
|
||||
post-processor "manifest" {
|
||||
output = "manifest-lite.json"
|
||||
strip_path = true
|
||||
}
|
||||
}
|
||||
261
DigitalOceanMarketplace/scripts/99-img-check-lite.sh
Normal file
261
DigitalOceanMarketplace/scripts/99-img-check-lite.sh
Normal file
@@ -0,0 +1,261 @@
|
||||
#!/bin/bash
|
||||
|
||||
# DigitalOcean Marketplace Image Validation Tool - Bitwarden Lite
|
||||
# © 2021-2022 DigitalOcean LLC.
|
||||
# This code is licensed under Apache 2.0 license (see LICENSE.md for details)
|
||||
|
||||
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
|
||||
|
||||
# $1 == command to check for
|
||||
# returns: 0 == true, 1 == false
|
||||
cmdExists() {
|
||||
if command -v "$1" > /dev/null 2>&1; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
function getDistro {
|
||||
if [ -f /etc/os-release ]; then
|
||||
# shellcheck disable=SC1091
|
||||
. /etc/os-release
|
||||
OS=$NAME
|
||||
VER=$VERSION_ID
|
||||
elif type lsb_release >/dev/null 2>&1; then
|
||||
OS=$(lsb_release -si)
|
||||
VER=$(lsb_release -sr)
|
||||
elif [ -f /etc/lsb-release ]; then
|
||||
# shellcheck disable=SC1091
|
||||
. /etc/lsb-release
|
||||
OS=$DISTRIB_ID
|
||||
VER=$DISTRIB_RELEASE
|
||||
elif [ -f /etc/debian_version ]; then
|
||||
OS=Debian
|
||||
VER=$(cat /etc/debian_version)
|
||||
else
|
||||
OS=$(uname -s)
|
||||
VER=$(uname -r)
|
||||
fi
|
||||
}
|
||||
|
||||
function loadPasswords {
|
||||
SHADOW=$(cat /etc/shadow)
|
||||
}
|
||||
|
||||
function checkAgent {
|
||||
if [ -d /opt/digitalocean ];then
|
||||
echo -en "\e[41m[FAIL]\e[0m DigitalOcean directory detected.\n"
|
||||
((FAIL++))
|
||||
STATUS=2
|
||||
if [[ $OS == "Ubuntu" ]] || [[ $OS == "Debian" ]]; then
|
||||
echo "To uninstall the agent and remove the DO directory: 'sudo apt-get purge droplet-agent'"
|
||||
fi
|
||||
else
|
||||
echo -en "\e[32m[PASS]\e[0m DigitalOcean Monitoring agent was not found\n"
|
||||
((PASS++))
|
||||
fi
|
||||
}
|
||||
|
||||
function checkLogs {
|
||||
echo -en "\nChecking for log files in /var/log\n\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
|
||||
for f in /var/log/*.log; do
|
||||
[[ -e $f ]] || break
|
||||
if [[ "$(wc -c < "${f}")" -gt 50 ]]; then
|
||||
echo -en "\e[93m[WARN]\e[0m un-cleared log file, ${f} found.\n"
|
||||
((WARN++))
|
||||
if [[ $STATUS != 2 ]]; then STATUS=1; fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
function checkRoot {
|
||||
user="root"
|
||||
uhome="/root"
|
||||
for usr in $SHADOW
|
||||
do
|
||||
IFS=':' read -r -a u <<< "$usr"
|
||||
if [[ "${u[0]}" == "${user}" ]]; then
|
||||
if [[ ${u[1]} == "!" ]] || [[ ${u[1]} == "!!" ]] || [[ ${u[1]} == "*" ]]; then
|
||||
echo -en "\e[32m[PASS]\e[0m User ${user} has no password set.\n"
|
||||
((PASS++))
|
||||
else
|
||||
echo -en "\e[41m[FAIL]\e[0m User ${user} has a password set on their account.\n"
|
||||
((FAIL++))
|
||||
STATUS=2
|
||||
fi
|
||||
fi
|
||||
done
|
||||
if [ -d ${uhome}/ ]; then
|
||||
if [ -d ${uhome}/.ssh/ ]; then
|
||||
if ls ${uhome}/.ssh/* > /dev/null 2>&1; then
|
||||
for key in "${uhome}"/.ssh/*
|
||||
do
|
||||
if [ "${key}" == "${uhome}/.ssh/authorized_keys" ]; then
|
||||
if [ "$(wc -c < "${key}")" -gt 50 ]; then
|
||||
echo -en "\e[41m[FAIL]\e[0m User \e[1m${user}\e[0m has a populated authorized_keys file in \e[93m${key}\e[0m\n"
|
||||
((FAIL++))
|
||||
STATUS=2
|
||||
fi
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo -en "\e[32m[ OK ]\e[0m User \e[1m${user}\e[0m has no SSH keys present\n"
|
||||
fi
|
||||
else
|
||||
echo -en "\e[32m[ OK ]\e[0m User \e[1m${user}\e[0m does not have an .ssh directory\n"
|
||||
fi
|
||||
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 ${user}'s Bash History appears to have been cleared\n"
|
||||
((PASS++))
|
||||
else
|
||||
echo -en "\e[41m[FAIL]\e[0m ${user}'s Bash History should be cleared to prevent sensitive information from leaking\n"
|
||||
((FAIL++))
|
||||
STATUS=2
|
||||
fi
|
||||
else
|
||||
echo -en "\e[32m[PASS]\e[0m The Root User's Bash History is not present\n"
|
||||
((PASS++))
|
||||
fi
|
||||
fi
|
||||
echo -en "\n\n"
|
||||
return 1
|
||||
}
|
||||
|
||||
function checkFirewall {
|
||||
if [[ $OS == "Ubuntu" ]]; then
|
||||
fw="ufw"
|
||||
ufwa=$(ufw status | head -1 | sed -e "s/^Status:\ //")
|
||||
if [[ $ufwa == "active" ]]; then
|
||||
FW_VER="\e[32m[PASS]\e[0m Firewall service (${fw}) is active\n"
|
||||
((PASS++))
|
||||
else
|
||||
FW_VER="\e[93m[WARN]\e[0m No firewall is configured. Ensure ${fw} is installed and configured\n"
|
||||
((WARN++))
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
function checkCloudInit {
|
||||
if hash cloud-init 2>/dev/null; then
|
||||
CI="\e[32m[PASS]\e[0m Cloud-init is installed.\n"
|
||||
((PASS++))
|
||||
else
|
||||
CI="\e[41m[FAIL]\e[0m No valid version of cloud-init was found.\n"
|
||||
((FAIL++))
|
||||
STATUS=2
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
function checkBitwardenLite {
|
||||
if [ -x /var/lib/cloud/scripts/per-instance/001_onboot ]; then
|
||||
echo -en "\e[32m[PASS]\e[0m Cloud-init first-boot script is present and executable.\n"
|
||||
((PASS++))
|
||||
else
|
||||
echo -en "\e[41m[FAIL]\e[0m Cloud-init first-boot script not found at /var/lib/cloud/scripts/per-instance/001_onboot.\n"
|
||||
((FAIL++))
|
||||
STATUS=2
|
||||
fi
|
||||
}
|
||||
|
||||
function checkDockerCompose {
|
||||
if docker compose version > /dev/null 2>&1; then
|
||||
echo -en "\e[32m[PASS]\e[0m Docker Compose plugin is installed.\n"
|
||||
((PASS++))
|
||||
else
|
||||
echo -en "\e[41m[FAIL]\e[0m Docker Compose plugin is not installed.\n"
|
||||
((FAIL++))
|
||||
STATUS=2
|
||||
fi
|
||||
}
|
||||
|
||||
clear
|
||||
echo "DigitalOcean Marketplace Image Validation Tool (Bitwarden Lite) ${VERSION}"
|
||||
echo "Executed on: ${RUNDATE}"
|
||||
echo "Checking local system for Marketplace compatibility..."
|
||||
|
||||
getDistro
|
||||
|
||||
echo -en "\n\e[1mDistribution:\e[0m ${OS}\n"
|
||||
echo -en "\e[1mVersion:\e[0m ${VER}\n\n"
|
||||
|
||||
if [[ $OS == "Ubuntu" ]] && [[ $VER == "24.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 24.04)\n"
|
||||
((FAIL++))
|
||||
STATUS=2
|
||||
fi
|
||||
|
||||
checkCloudInit
|
||||
echo -en "${CI}"
|
||||
|
||||
checkFirewall
|
||||
echo -en "${FW_VER}"
|
||||
|
||||
checkDockerCompose
|
||||
|
||||
loadPasswords
|
||||
checkLogs
|
||||
|
||||
echo -en "\n\nChecking the root account...\n"
|
||||
checkRoot
|
||||
|
||||
checkAgent
|
||||
|
||||
checkBitwardenLite
|
||||
|
||||
# Summary
|
||||
echo -en "\n\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\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 "We did not detect any issues with this image.\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 "Some critical tests failed. These items must be resolved and this scan re-run before you submit your image to the DigitalOcean Marketplace.\n\n"
|
||||
exit 1
|
||||
fi
|
||||
@@ -16,6 +16,6 @@ $ cosign verify ghcr.io/bitwarden/$IMAGE_NAME:latest --certificate-identity-rege
|
||||
## Quick Deploy
|
||||
[](https://marketplace.digitalocean.com/apps/bitwarden?action=deploy)
|
||||
|
||||
[](https://portal.azure.com/#create/bitwarden.bitwarden-self-host)
|
||||
[](https://portal.azure.com/#create/bitwardeninc.bitwarden-self-host)
|
||||
|
||||
[Deploy to Kubernetes with Helm](https://github.com/bitwarden/helm-charts/blob/main/charts/self-host/README.md)
|
||||
|
||||
Reference in New Issue
Block a user