mirror of
https://github.com/Manoj-HV30/cloud-vps.git
synced 2026-07-13 00:50:10 +00:00
fixed structure
This commit is contained in:
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
all:
|
||||
hosts:
|
||||
vps:
|
||||
ansible_host: <YOUR_VPS_IP_HERE>
|
||||
ansible_user: papyrus
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,6 @@
|
||||
name: azure-vps
|
||||
runtime:
|
||||
name: python
|
||||
options:
|
||||
virtualenv: venv
|
||||
description: Personal VPS on Azure with Pulumi + Ansible
|
||||
@@ -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))
|
||||
Executable
+57
@@ -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"
|
||||
Reference in New Issue
Block a user