fixed structure

This commit is contained in:
2026-05-17 06:34:17 +05:30
parent 5ce03220d4
commit 59c4b7a949
14 changed files with 433 additions and 3 deletions
+4 -2
View File
@@ -1,9 +1,11 @@
# Pulumi # Pulumi
azure/venv/
venv/ venv/
*.pyc *.pyc
__pycache__/ __pycache__/
# Pulumi state (if using local backend) # Pulumi state (if using local backend)
azure/.pulumi/
.pulumi/ .pulumi/
# Secrets # Secrets
@@ -16,5 +18,5 @@ __pycache__/
.DS_Store .DS_Store
# Local configurations (DO NOT COMMIT) # Local configurations (DO NOT COMMIT)
Pulumi.dev.yaml azure/Pulumi.dev.yaml
ansible/inventory.yml azure/ansible/inventory.yml
+5 -1
View File
@@ -31,8 +31,9 @@ cd cloud-vps
2. **Choose Your Cloud Provider:** This blueprint is natively written for **Microsoft Azure**, but the Ansible logic is completely cloud-agnostic. Ensure you have the Azure CLI installed and have run `az login` to authenticate. 2. **Choose Your Cloud Provider:** This blueprint is natively written for **Microsoft Azure**, but the Ansible logic is completely cloud-agnostic. Ensure you have the Azure CLI installed and have run `az login` to authenticate.
3. **Configure the Blueprint:** Copy the example configuration files and modify `Pulumi.dev.yaml` with your preferred region, VM size, and local SSH key path: 3. **Configure the Blueprint:** Navigate into the `azure/` directory, copy the example configuration files, and modify `Pulumi.dev.yaml` with your preferred region, VM size, and local SSH key path:
```bash ```bash
cd azure
cp Pulumi.dev.example.yaml Pulumi.dev.yaml cp Pulumi.dev.example.yaml Pulumi.dev.yaml
cp ansible/inventory.example.yml ansible/inventory.yml cp ansible/inventory.example.yml ansible/inventory.yml
``` ```
@@ -70,12 +71,15 @@ You can deploy the cloud infrastructure using the automated script or run the st
**Option A: Automated Deployment** **Option A: Automated Deployment**
```bash ```bash
cd azure
chmod +x scripts/deploy.sh chmod +x scripts/deploy.sh
./scripts/deploy.sh ./scripts/deploy.sh
``` ```
**Option B: Manual Deployment** **Option B: Manual Deployment**
```bash ```bash
cd azure
# 1. Provision cloud resources # 1. Provision cloud resources
pulumi up --stack dev pulumi up --stack dev
+10
View File
@@ -0,0 +1,10 @@
[defaults]
inventory = inventory.yml
remote_user = papyrus
private_key_file = ~/.ssh/id_ed25519
host_key_checking = False
retry_files_enabled = False
[privilege_escalation]
become = True
become_method = sudo
+5
View File
@@ -0,0 +1,5 @@
all:
hosts:
vps:
ansible_host: <YOUR_VPS_IP_HERE>
ansible_user: papyrus
+11
View File
@@ -0,0 +1,11 @@
---
# Main playbook — configures a fresh VPS with Docker, security, and backup
- name: Configure VPS
hosts: all
become: true
roles:
- base
- security
- docker
- backup
+25
View File
@@ -0,0 +1,25 @@
---
# Restic backup setup — installs restic and deploys the backup script
- name: Install restic
apt:
name: restic
state: present
- name: Create secrets directory
file:
path: /srv/secrets
state: directory
mode: "0700"
- name: Deploy backup script
template:
src: backup.sh.j2
dest: /usr/local/bin/backup.sh
mode: "0700"
- name: Setup daily backup cron (3 AM)
cron:
name: "restic daily backup"
hour: "3"
minute: "0"
job: "/usr/local/bin/backup.sh >> /var/log/backup.log 2>&1"
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# Managed by Ansible — do not edit manually
export B2_ACCOUNT_ID="{{ b2_account_id | default('YOUR_KEY_ID') }}"
export B2_ACCOUNT_KEY="{{ b2_account_key | default('YOUR_APPLICATION_KEY') }}"
export RESTIC_PASSWORD="{{ restic_password | default('YOUR_BACKUP_PASSWORD') }}"
REPO="b2:{{ b2_bucket | default('your-bucket') }}:/backup"
restic -r "$REPO" backup \
--exclude /home/papyrus/apps/navidrome/music \
/var/lib/docker/volumes \
/srv/secrets \
/home/papyrus/apps
restic -r "$REPO" forget --keep-last 7 --keep-weekly 4 --prune
+24
View File
@@ -0,0 +1,24 @@
---
# Base packages and system updates
- name: Update apt cache
apt:
update_cache: true
cache_valid_time: 3600
- name: Upgrade all packages
apt:
upgrade: dist
- name: Install essential packages
apt:
name:
- curl
- git
- unzip
- vim
- htop
- wget
- ca-certificates
- gnupg
- lsb-release
state: present
+47
View File
@@ -0,0 +1,47 @@
---
# Docker CE + Docker Compose v2 plugin
- name: Remove old Docker packages
apt:
name:
- docker
- docker-engine
- docker.io
- containerd
- runc
state: absent
- name: Add Docker GPG key
ansible.builtin.get_url:
url: https://download.docker.com/linux/ubuntu/gpg
dest: /etc/apt/keyrings/docker.asc
mode: "0644"
- name: Add Docker apt repository
ansible.builtin.apt_repository:
repo: >-
deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc]
https://download.docker.com/linux/ubuntu
{{ ansible_distribution_release }} stable
state: present
- name: Install Docker CE
apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-compose-plugin
state: present
update_cache: true
- name: Enable and start Docker
systemd:
name: docker
enabled: true
state: started
- name: Add admin user to docker group
user:
name: "{{ ansible_user }}"
groups: docker
append: true
@@ -0,0 +1,46 @@
---
# Security hardening — UFW firewall + fail2ban
- name: Install security packages
apt:
name:
- ufw
- fail2ban
state: present
- name: Set UFW default deny incoming
community.general.ufw:
direction: incoming
default: deny
- name: Set UFW default allow outgoing
community.general.ufw:
direction: outgoing
default: allow
- name: Allow SSH through UFW
community.general.ufw:
rule: allow
port: "22"
proto: tcp
- name: Allow HTTP through UFW
community.general.ufw:
rule: allow
port: "80"
proto: tcp
- name: Allow HTTPS through UFW
community.general.ufw:
rule: allow
port: "443"
proto: tcp
- name: Enable UFW
community.general.ufw:
state: enabled
- name: Enable and start fail2ban
systemd:
name: fail2ban
enabled: true
state: started
+5
View File
@@ -0,0 +1,5 @@
config:
azure-native:location: southeastasia
azure-vps:vm_size: Standard_B1ms
azure-vps:admin_username: papyrus
azure-vps:ssh_public_key_path: ~/.ssh/id_ed25519.pub
+6
View File
@@ -0,0 +1,6 @@
name: azure-vps
runtime:
name: python
options:
virtualenv: venv
description: Personal VPS on Azure with Pulumi + Ansible
+172
View File
@@ -0,0 +1,172 @@
"""
Azure VPS — Pulumi infrastructure
Creates a single Linux VM with Docker-ready networking.
"""
import pulumi
from pulumi import Config, Output, export
from pulumi_azure_native import resources, network, compute
from pathlib import Path
# ---------------------------------------------------------------------------
# Config
# ---------------------------------------------------------------------------
config = Config("azure-vps")
VM_SIZE = config.get("vm_size") or "Standard_B1ms"
ADMIN_USERNAME = config.get("admin_username") or "papyrus"
SSH_KEY_PATH = config.get("ssh_public_key_path") or "~/.ssh/id_ed25519.pub"
ssh_public_key = Path(SSH_KEY_PATH).expanduser().read_text().strip()
# ---------------------------------------------------------------------------
# Resource Group
# ---------------------------------------------------------------------------
rg = resources.ResourceGroup(
"rg",
resource_group_name="papyrus-vm-rg",
tags={"environment": "dev", "managed_by": "pulumi"},
)
# ---------------------------------------------------------------------------
# Networking — VNet, Subnet, Public IP, NSG, NIC
# ---------------------------------------------------------------------------
vnet = network.VirtualNetwork(
"vnet",
virtual_network_name="papyrus-vnet",
resource_group_name=rg.name,
address_space=network.AddressSpaceArgs(
address_prefixes=["10.0.0.0/16"],
),
)
subnet = network.Subnet(
"subnet",
subnet_name="papyrus-subnet",
resource_group_name=rg.name,
virtual_network_name=vnet.name,
address_prefix="10.0.1.0/24",
)
public_ip = network.PublicIPAddress(
"pip",
public_ip_address_name="papyrus-pip",
resource_group_name=rg.name,
public_ip_allocation_method=network.IPAllocationMethod.STATIC,
sku=network.PublicIPAddressSkuArgs(
name=network.PublicIPAddressSkuName.STANDARD,
),
)
nsg = network.NetworkSecurityGroup(
"nsg",
network_security_group_name="papyrus-nsg",
resource_group_name=rg.name,
security_rules=[
network.SecurityRuleArgs(
name="SSH",
priority=1000,
direction=network.SecurityRuleDirection.INBOUND,
access=network.SecurityRuleAccess.ALLOW,
protocol=network.SecurityRuleProtocol.TCP,
source_port_range="*",
destination_port_range="22",
source_address_prefix="*",
destination_address_prefix="*",
),
network.SecurityRuleArgs(
name="HTTP",
priority=1010,
direction=network.SecurityRuleDirection.INBOUND,
access=network.SecurityRuleAccess.ALLOW,
protocol=network.SecurityRuleProtocol.TCP,
source_port_range="*",
destination_port_range="80",
source_address_prefix="*",
destination_address_prefix="*",
),
network.SecurityRuleArgs(
name="HTTPS",
priority=1020,
direction=network.SecurityRuleDirection.INBOUND,
access=network.SecurityRuleAccess.ALLOW,
protocol=network.SecurityRuleProtocol.TCP,
source_port_range="*",
destination_port_range="443",
source_address_prefix="*",
destination_address_prefix="*",
),
],
)
nic = network.NetworkInterface(
"nic",
network_interface_name="papyrus-nic",
resource_group_name=rg.name,
ip_configurations=[
network.NetworkInterfaceIPConfigurationArgs(
name="papyrus-ipconfig",
subnet=network.SubnetArgs(id=subnet.id),
public_ip_address=network.PublicIPAddressArgs(id=public_ip.id),
private_ip_allocation_method=network.IPAllocationMethod.DYNAMIC,
),
],
network_security_group=network.NetworkSecurityGroupArgs(id=nsg.id),
)
# ---------------------------------------------------------------------------
# Virtual Machine — Ubuntu 24.04 LTS
# ---------------------------------------------------------------------------
vm = compute.VirtualMachine(
"vm",
vm_name="papyrus-vm",
resource_group_name=rg.name,
hardware_profile=compute.HardwareProfileArgs(
vm_size=VM_SIZE,
),
os_profile=compute.OSProfileArgs(
computer_name="papyrus-vm",
admin_username=ADMIN_USERNAME,
linux_configuration=compute.LinuxConfigurationArgs(
disable_password_authentication=True,
ssh=compute.SshConfigurationArgs(
public_keys=[
compute.SshPublicKeyArgs(
path=f"/home/{ADMIN_USERNAME}/.ssh/authorized_keys",
key_data=ssh_public_key,
),
],
),
),
),
network_profile=compute.NetworkProfileArgs(
network_interfaces=[
compute.NetworkInterfaceReferenceArgs(
id=nic.id,
primary=True,
),
],
),
storage_profile=compute.StorageProfileArgs(
os_disk=compute.OSDiskArgs(
create_option=compute.DiskCreateOptionTypes.FROM_IMAGE,
managed_disk=compute.ManagedDiskParametersArgs(
storage_account_type=compute.StorageAccountTypes.STANDARD_LRS,
),
),
image_reference=compute.ImageReferenceArgs(
publisher="Canonical",
offer="ubuntu-24_04-lts",
sku="server",
version="latest",
),
),
tags={"environment": "dev", "managed_by": "pulumi"},
)
# ---------------------------------------------------------------------------
# Outputs
# ---------------------------------------------------------------------------
export("vm_public_ip", public_ip.ip_address)
export("admin_username", ADMIN_USERNAME)
export("ssh_command", Output.concat("ssh ", ADMIN_USERNAME, "@", public_ip.ip_address))
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
# One-command deploy: pulumi up → ansible provision
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
echo "=== Step 1: Pulumi — creating Azure infrastructure ==="
cd "$PROJECT_DIR"
pulumi up --yes --stack dev
# grab outputs
VM_IP=$(pulumi stack output vm_public_ip --stack dev)
ADMIN_USER=$(pulumi stack output admin_username --stack dev)
echo ""
echo "VM is up at: $VM_IP"
echo "User: $ADMIN_USER"
echo ""
echo "=== Step 2: Generating Ansible inventory ==="
if [ ! -f "$PROJECT_DIR/ansible/inventory.yml" ]; then
cat > "$PROJECT_DIR/ansible/inventory.yml" <<EOF
all:
hosts:
vps:
ansible_host: ${VM_IP}
ansible_user: ${ADMIN_USER}
EOF
else
# Update the IP without destroying the user's custom variables
sed -i "s/ansible_host: .*/ansible_host: ${VM_IP}/" "$PROJECT_DIR/ansible/inventory.yml"
fi
echo "Inventory written to ansible/inventory.yml"
echo ""
echo "=== Step 3: Waiting for SSH to be ready ==="
MAX_RETRIES=30
for i in $(seq 1 $MAX_RETRIES); do
if ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no "${ADMIN_USER}@${VM_IP}" "echo ok" &>/dev/null; then
echo "SSH is ready!"
break
fi
echo " waiting for SSH... (attempt $i/$MAX_RETRIES)"
sleep 10
done
echo ""
echo "=== Step 4: Ansible — configuring the VPS ==="
cd "$PROJECT_DIR/ansible"
ansible-playbook playbook.yml
echo ""
echo "=== Done! ==="
echo "SSH in: ssh ${ADMIN_USER}@${VM_IP}"
echo "Next: clone your repos and docker compose up"