Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions algo
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ case "$1" in
echo "Commands:"
echo " (default) Deploy a new VPN server"
echo " update-users Add or remove users on an existing server"
echo " destroy Destroy a deployed server and clean up configs"
echo " list-servers List deployed servers (JSON output)"
echo ""
echo "Configuration:"
echo " Edit config.cfg to set users, DNS, and VPN options before deploying."
Expand All @@ -186,6 +188,24 @@ case "$1" in
;;
update-users)
uv run ansible-playbook users.yml "${@:2}" -t update-users ;;
destroy)
if [ -z "${2:-}" ] || [[ "$2" == -* ]]; then
echo "Usage: ./algo destroy <server-ip> [ANSIBLE_OPTIONS]"
echo ""
echo "Destroy a deployed Algo VPN server and remove local configs."
echo ""
echo "Arguments:"
echo " server-ip IP address of the server to destroy"
echo ""
echo "Examples:"
echo " ./algo destroy 188.166.66.185"
echo " ./algo destroy 52.1.2.3 -e \"region=us-east-1\""
echo " ./algo destroy 188.166.66.185 -e \"confirm_destroy=true\""
exit 1
fi
uv run ansible-playbook destroy.yml -e "server_ip=$2" "${@:3}" ;;
list-servers)
uv run python3 scripts/list_servers.py "${@:2}" ;;
*)
uv run ansible-playbook main.yml "${@}" ;;
esac
144 changes: 144 additions & 0 deletions destroy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
---
- name: Destroy an Algo VPN server
hosts: localhost
gather_facts: false
become: false
vars_files:
- config.cfg

tasks:
- block:
- name: Validate server_ip is provided
assert:
that: server_ip is defined and server_ip | length > 0
fail_msg: |
server_ip is required. Usage:
./algo destroy <server-ip>
ansible-playbook destroy.yml -e "server_ip=YOUR_SERVER_IP"

- name: Check that server config exists
stat:
path: "configs/{{ server_ip }}/.config.yml"
register: _server_config

- name: Fail if server config not found
fail:
msg: |
No config found at configs/{{ server_ip }}/.config.yml

This server may not have been deployed by Algo, or
its configs were already removed.

Known servers:
ls configs/*/
when: not _server_config.stat.exists

- name: Load server configuration
include_vars:
file: "configs/{{ server_ip }}/.config.yml"
name: _server_cfg

- name: Set provider and server name from config
set_fact:
algo_provider: "{{ _server_cfg.algo_provider }}"
algo_server_name: "{{ _server_cfg.algo_server_name }}"

- name: Validate required config values
assert:
that:
- algo_provider is defined and algo_provider | length > 0
- algo_server_name is defined and algo_server_name | length > 0
fail_msg: |
Server config is missing algo_provider or algo_server_name.
Check configs/{{ server_ip }}/.config.yml

- name: Install cloud provider dependencies
shell: "uv pip install '.[{{ _provider_extras[algo_provider] | default(algo_provider) }}]'"
vars:
_provider_extras:
ec2: aws
lightsail: aws
azure: azure
gce: gcp
hetzner: hetzner
linode: linode
openstack: openstack
cloudstack: cloudstack
when: algo_provider != "local"
changed_when: false

- name: Set region from stored config
set_fact:
region: "{{ _server_cfg.algo_region }}"
when:
- region is not defined
- _server_cfg.algo_region is defined
- _server_cfg.algo_region | length > 0

- name: Validate region for providers that require it
fail:
msg: |
Region is required to destroy {{ algo_provider }} servers.
Pass it with: -e "region=YOUR_REGION"

Example:
./algo destroy {{ server_ip }} -e "region=us-east-1"
when:
- algo_provider in ['ec2', 'lightsail', 'gce', 'scaleway', 'vultr']
- region is not defined

- name: Set dummy region for providers that do not need it
set_fact:
region: "unused"
when:
- region is not defined
- algo_provider not in ['ec2', 'lightsail', 'gce', 'scaleway', 'vultr']

- name: Gather provider credentials
include_tasks: "roles/cloud-{{ algo_provider }}/tasks/prompts.yml"
when: algo_provider != "local"

- name: Display destroy plan
debug:
msg:
- "Server IP: {{ server_ip }}"
- "Server name: {{ algo_server_name }}"
- "Provider: {{ algo_provider }}"

- name: Confirm destruction
pause:
prompt: |
This will permanently destroy the server and remove local configs.
Type 'yes' to confirm
register: _confirm_destroy
when: confirm_destroy is not defined or not confirm_destroy | bool

- name: Abort if not confirmed
fail:
msg: "Destroy aborted by user."
when:
- confirm_destroy is not defined or not confirm_destroy | bool
- _confirm_destroy.user_input | default('') | lower != 'yes'

- name: Destroy cloud resources
include_tasks: "roles/cloud-{{ algo_provider }}/tasks/destroy.yml"
when: algo_provider != "local"

- name: Remove local config directory
file:
path: "configs/{{ server_ip }}"
state: absent

- name: Remove localhost symlink
file:
path: configs/localhost
state: absent
when: server_ip == "localhost"

- name: Destroy complete
debug:
msg:
- "Server {{ algo_server_name }} ({{ server_ip }}) destroyed."
- "Local configs removed from configs/{{ server_ip }}/"
rescue:
- include_tasks: playbooks/rescue.yml
15 changes: 11 additions & 4 deletions library/scaleway_compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,8 @@ def core(module):

compute_api = Scaleway(module=module)

check_image_id(compute_api, wished_server["image"])
if wished_server["state"] != "absent":
check_image_id(compute_api, wished_server["image"])

# IP parameters of the wished server depends on the configuration
ip_payload = public_ip_payload(compute_api=compute_api, public_ip=module.params["public_ip"])
Expand All @@ -648,16 +649,16 @@ def main():
argument_spec = scaleway_argument_spec()
argument_spec.update(
dict(
image=dict(required=True),
image=dict(),
name=dict(),
region=dict(required=True, choices=SCALEWAY_LOCATION.keys()),
commercial_type=dict(required=True),
commercial_type=dict(),
enable_ipv6=dict(default=False, type="bool"),
boot_type=dict(choices=["bootscript", "local"]),
public_ip=dict(default="absent"),
state=dict(choices=state_strategy.keys(), default="present"),
tags=dict(type="list", default=[]),
organization=dict(required=True),
organization=dict(),
wait=dict(type="bool", default=False),
wait_timeout=dict(type="int", default=300),
wait_sleep_time=dict(type="int", default=3),
Expand All @@ -666,6 +667,12 @@ def main():
)
module = AnsibleModule(
argument_spec=argument_spec,
required_if=[
("state", "present", ["image", "commercial_type", "organization"]),
("state", "running", ["image", "commercial_type", "organization"]),
("state", "stopped", ["image", "commercial_type", "organization"]),
("state", "restarted", ["image", "commercial_type", "organization"]),
],
supports_check_mode=True,
)

Expand Down
10 changes: 10 additions & 0 deletions roles/cloud-azure/tasks/destroy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
- name: Destroy Azure resource group
azure.azcollection.azure_rm_resourcegroup:
name: "{{ algo_server_name }}"
state: absent
force_delete_nonempty: true
secret: "{{ secret }}"
tenant: "{{ tenant }}"
client_id: "{{ client_id }}"
subscription_id: "{{ subscription_id }}"
17 changes: 17 additions & 0 deletions roles/cloud-cloudstack/tasks/destroy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
- environment:
CLOUDSTACK_KEY: "{{ algo_cs_key }}"
CLOUDSTACK_SECRET: "{{ algo_cs_token }}"
CLOUDSTACK_ENDPOINT: "{{ algo_cs_url }}"
no_log: true
block:
- name: Destroy CloudStack instance
cs_instance:
name: "{{ algo_server_name }}"
state: expunged

- name: Remove security group
cs_securitygroup:
name: "{{ algo_server_name }}-security_group"
state: absent
failed_when: false
7 changes: 7 additions & 0 deletions roles/cloud-digitalocean/tasks/destroy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
- name: Destroy DigitalOcean droplet
digital_ocean_droplet:
state: absent
name: "{{ algo_server_name }}"
oauth_token: "{{ algo_do_token }}"
unique_name: true
14 changes: 14 additions & 0 deletions roles/cloud-ec2/tasks/destroy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
- name: Set stack name
set_fact:
stack_name: "{{ algo_server_name | replace('.', '-') }}"

- name: Destroy CloudFormation stack
cloudformation:
aws_access_key: "{{ access_key }}"
aws_secret_key: "{{ secret_key }}"
aws_session_token: "{{ session_token if session_token else omit }}"
stack_name: "{{ stack_name }}"
state: absent
region: "{{ algo_region }}"
no_log: true
55 changes: 55 additions & 0 deletions roles/cloud-gce/tasks/destroy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
- name: Get zones
gcp_compute_location_info:
auth_kind: serviceaccount
service_account_file: "{{ credentials_file_path }}"
project: "{{ project_id }}"
scope: zones
filters:
- name={{ algo_region }}-*
- status=UP
register: gcp_compute_zone_info

- name: Set zone
set_fact:
algo_zone: >-
{{ (gcp_compute_zone_info.resources |
random(seed=algo_server_name + algo_region + project_id)
).name }}

- name: Destroy GCE instance
gcp_compute_instance:
auth_kind: serviceaccount
service_account_file: "{{ credentials_file_path }}"
project: "{{ project_id }}"
name: "{{ algo_server_name }}"
zone: "{{ algo_zone }}"
state: absent

- name: Remove static IP
gcp_compute_address:
auth_kind: serviceaccount
service_account_file: "{{ credentials_file_path }}"
project: "{{ project_id }}"
name: "{{ algo_server_name }}"
region: "{{ algo_region }}"
state: absent
failed_when: false

- name: Remove firewall rule
gcp_compute_firewall:
auth_kind: serviceaccount
service_account_file: "{{ credentials_file_path }}"
project: "{{ project_id }}"
name: algovpn
state: absent
failed_when: false

- name: Remove network
gcp_compute_network:
auth_kind: serviceaccount
service_account_file: "{{ credentials_file_path }}"
project: "{{ project_id }}"
name: algovpn
state: absent
failed_when: false
6 changes: 6 additions & 0 deletions roles/cloud-hetzner/tasks/destroy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
- name: Destroy Hetzner server
hetzner.hcloud.server:
name: "{{ algo_server_name }}"
state: absent
api_token: "{{ algo_hcloud_token }}"
13 changes: 13 additions & 0 deletions roles/cloud-lightsail/tasks/destroy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
- name: Set stack name
set_fact:
stack_name: "{{ algo_server_name | replace('.', '-') }}"

- name: Destroy CloudFormation stack
cloudformation:
aws_access_key: "{{ access_key }}"
aws_secret_key: "{{ secret_key }}"
stack_name: "{{ stack_name }}"
state: absent
region: "{{ algo_region }}"
no_log: true
6 changes: 6 additions & 0 deletions roles/cloud-linode/tasks/destroy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
- name: Destroy Linode instance
linode.cloud.instance:
api_token: "{{ algo_linode_token }}"
label: "{{ algo_server_name }}"
state: absent
11 changes: 11 additions & 0 deletions roles/cloud-openstack/tasks/destroy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
- name: Destroy OpenStack server
openstack.cloud.server:
state: absent
name: "{{ algo_server_name }}"

- name: Remove security group
openstack.cloud.security_group:
state: absent
name: "{{ algo_server_name }}-security_group"
failed_when: false
9 changes: 9 additions & 0 deletions roles/cloud-scaleway/tasks/destroy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
- environment:
SCW_TOKEN: "{{ algo_scaleway_token }}"
block:
- name: Destroy Scaleway server
scaleway_compute:
name: "{{ algo_server_name }}"
state: absent
region: "{{ algo_region }}"
15 changes: 15 additions & 0 deletions roles/cloud-vultr/tasks/destroy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
- environment:
VULTR_API_KEY: "{{ lookup('ini', 'key', section='default', file=algo_vultr_config) }}"
block:
- name: Destroy Vultr instance
vultr.cloud.instance:
name: "{{ algo_server_name }}"
region: "{{ algo_vultr_region }}"
state: absent

- name: Remove firewall group
vultr.cloud.firewall_group:
name: "{{ algo_server_name }}"
state: absent
failed_when: false
Loading