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
13 changes: 8 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
# Secrets — never commit real credentials
vault_password.txt
**/secrets.yml
**/vault_password.txt
**/*.secret
**/*.vault

.vscode
.ansible
roles/hosts.yml
roles/sing-box/defaults/secrets.yml
roles/sing-box-playbook/defaults/secrets.yml
roles/hosts.yml
roles/xray/defaults/secrets.yml
roles/xray/defaults/vic_secret.yml

# Generated by tests/run.sh
tests/.cache/
tests/.output/
tests/fixtures/test_secrets.yml


CLAUDE.md
26 changes: 15 additions & 11 deletions roles/sing-box-playbook/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Default variables for the sing-box role with Hysteria2

# --- Sing-box Download Variables ---
singbox_version: "1.12.21"
singbox_version: "1.13.3"
singbox_auto_update: true
singbox_repo: "SagerNet/sing-box"
singbox_architecture: "amd64" # Options: amd64, arm64, armv7
Expand All @@ -12,16 +12,9 @@ singbox_config_dir: "/etc/sing-box"
singbox_user: "nobody"

# --- DNS Settings ---
singbox_dns_servers:
- tag: "google"
address: "tls://8.8.8.8"
strategy: "prefer_ipv4"
- tag: "cloudflare"
address: "tls://1.1.1.1"
strategy: "prefer_ipv4"
- tag: "local"
address: "local"
detour: "direct"
# sing-box 1.12+ format: type + server fields (address: prefix is legacy)
singbox_dns_strategy: "prefer_ipv4" # global: prefer_ipv4, prefer_ipv6, ipv4_only, ipv6_only
singbox_dns_final: "local" # default server tag when no rule matches

# --- Log Settings ---
singbox_log_disabled: false
Expand Down Expand Up @@ -93,4 +86,15 @@ singbox_geosite_enabled: true
singbox_geosite_url: "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db"
singbox_geosite_path: "/etc/sing-box/geosite.db"

# --- Raven-subscribe integration ---
# When enabled, Raven-subscribe will sync Hysteria2 users from sing-box config.
# This variable is consumed by the xray role's raven-subscribe/config.json.j2 template.
# Set raven_subscribe_singbox_config to point to sing-box config file.
# Set raven_subscribe_singbox_enabled: true to activate sync.
#
# These variables are only effective when both roles (xray + sing-box) are deployed together.
# If deploying sing-box standalone, configure Raven manually.
raven_subscribe_singbox_config: "{{ singbox_config_dir }}/config.json"
raven_subscribe_singbox_enabled: true

# --- System Packages ---
21 changes: 21 additions & 0 deletions roles/sing-box-playbook/defaults/secrets.yml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
# secrets.yml — encrypt with ansible-vault:
# ansible-vault encrypt roles/sing-box-playbook/defaults/secrets.yml

# --- Hysteria2 users ---
# At least one user required.
singbox_hysteria2_users:
- name: "user@example.com"
password: "change-me-strong-password"

# --- Obfuscation (required when singbox_hysteria2_obfs_enabled: true) ---
singbox_hysteria2_obfs_password: "change-me-obfs-password"

# --- TLS / ACME ---
singbox:
tls_enabled: true
tls_server_name: "your-server.com" # Public domain pointing to this server
tls_acme_domain: "your-server.com" # Domain for Let's Encrypt certificate
tls_acme_email: "admin@your-server.com" # Email for Let's Encrypt notifications
tls_acme_provider: "letsencrypt" # letsencrypt or zerossl
tls_acme_data_directory: "/etc/sing-box/acme"
15 changes: 14 additions & 1 deletion roles/sing-box-playbook/handlers/main.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,35 @@
# handlers/main.yml
# Handlers for the sing-box role
# IMPORTANT: Ansible executes handlers in definition order, not notification order.
# Validate must come BEFORE Restart so invalid configs are caught before reload.

- name: Validate sing-box config
ansible.builtin.command:
cmd: "{{ singbox_install_dir }}/sing-box check -c {{ singbox_config_dir }}/config.json"
register: singbox_validate_result
changed_when: false
failed_when: singbox_validate_result.rc != 0
when: ansible_facts['service_mgr'] in ['systemd', 'openrc']

- name: Reload systemd and restart sing-box
ansible.builtin.systemd:
daemon_reload: true
name: sing-box
state: restarted
enabled: true
when: ansible_facts['service_mgr'] == "systemd"
listen: "Reload systemd and restart sing-box"

- name: Restart sing-box service
ansible.builtin.systemd:
name: sing-box
state: restarted
daemon_reload: true
when: ansible_facts['service_mgr'] == "systemd"
listen: "Restart sing-box service"

- name: Reload sing-box configuration
ansible.builtin.systemd:
name: sing-box
state: reloaded
when: ansible_facts['service_mgr'] == "systemd"
listen: "Reload sing-box configuration"
51 changes: 51 additions & 0 deletions roles/sing-box-playbook/tasks/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
- name: sing-box | Create configuration directory
ansible.builtin.file:
path: "{{ singbox_config_dir }}"
state: directory
owner: root
group: root
mode: "0755"

- name: sing-box | Create ACME certificate directory
ansible.builtin.file:
path: "{{ singbox.tls_acme_data_directory }}"
state: directory
owner: root
group: root
mode: "0700"
when: singbox.tls_enabled | default(true)

- name: sing-box | Create log directory
ansible.builtin.file:
path: "{{ singbox_log_output | dirname }}"
state: directory
owner: root
group: root
mode: "0755"

- name: sing-box | Download GeoIP database
ansible.builtin.get_url:
url: "{{ singbox_geoip_url }}"
dest: "{{ singbox_geoip_path }}"
mode: "0644"
timeout: 300
when: singbox_geoip_enabled

- name: sing-box | Download GeoSite database
ansible.builtin.get_url:
url: "{{ singbox_geosite_url }}"
dest: "{{ singbox_geosite_path }}"
mode: "0644"
timeout: 300
when: singbox_geosite_enabled

- name: sing-box | Generate configuration from template
ansible.builtin.template:
src: config.json.j2
dest: "{{ singbox_config_dir }}/config.json"
owner: root
group: root
mode: "0644"
validate: "{{ singbox_install_dir }}/sing-box check -c %s"
notify: Restart sing-box service
64 changes: 64 additions & 0 deletions roles/sing-box-playbook/tasks/install.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
- name: sing-box | Get latest release from GitHub
ansible.builtin.uri:
url: "https://api.github.com/repos/{{ singbox_repo }}/releases/latest"
return_content: true
headers:
Accept: "application/vnd.github.v3+json"
register: singbox_latest_release
when: singbox_auto_update
ignore_errors: true

- name: sing-box | Set version from latest release
ansible.builtin.set_fact:
singbox_version: "{{ singbox_latest_release.json.tag_name | default(singbox_version) | regex_replace('^v', '') }}"
when: singbox_auto_update and singbox_latest_release is succeeded

- name: sing-box | Display version to be installed
ansible.builtin.debug:
msg: "Installing sing-box version: {{ singbox_version }}"

- name: sing-box | Create installation directory
ansible.builtin.file:
path: "{{ singbox_install_dir }}"
state: directory
mode: "0755"

- name: sing-box | Download archive
ansible.builtin.get_url:
url: "{{ singbox_url }}/v{{ singbox_version }}/sing-box-{{ singbox_version }}-linux-{{ singbox_architecture }}.tar.gz"
dest: "/tmp/sing-box-{{ singbox_version }}.tar.gz"
mode: "0644"
timeout: 300

- name: sing-box | Extract archive
ansible.builtin.unarchive:
src: "/tmp/sing-box-{{ singbox_version }}.tar.gz"
dest: "/tmp"
remote_src: true

- name: sing-box | Install binary
ansible.builtin.copy:
src: "/tmp/sing-box-{{ singbox_version }}-linux-{{ singbox_architecture }}/sing-box"
dest: "{{ singbox_install_dir }}/sing-box"
remote_src: true
mode: "0755"
owner: root
group: root

- name: sing-box | Clean up downloaded files
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop:
- "/tmp/sing-box-{{ singbox_version }}.tar.gz"
- "/tmp/sing-box-{{ singbox_version }}-linux-{{ singbox_architecture }}"

- name: sing-box | Verify installation
ansible.builtin.command: "{{ singbox_install_dir }}/sing-box version"
register: singbox_version_output
changed_when: false

- name: sing-box | Display installed version
ansible.builtin.debug:
msg: "{{ singbox_version_output.stdout }}"
Loading
Loading