diff --git a/.github/workflows/CD-production-frontman.yml b/.github/workflows/CD-production-frontman.yml deleted file mode 100644 index c79b3ca..0000000 --- a/.github/workflows/CD-production-frontman.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: CD | Production Frontman - -concurrency: - group: production-frontman - cancel-in-progress: true - -on: - workflow_dispatch: - workflow_call: - -permissions: - contents: read - pages: write - id-token: write - -jobs: - deploy-frontman: - name: Deploy on production - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Render USERS_CONFIGS secret into config/users_configs.yml - env: - USERS_CONFIGS: ${{ secrets.USERS_CONFIGS }} - shell: bash - run: | - echo "$USERS_CONFIGS" > config/users-configs.yml - - - name: Render SERVERS secret into config/servers.yml - env: - SERVERS: ${{ secrets.SERVERS }} - shell: bash - run: | - echo "$SERVERS" > config/servers.yml - - - name: Render SUPABASE secret into config/supabase.yml - env: - SUPABASE: ${{ secrets.SUPABASE }} - shell: bash - run: | - echo "$SUPABASE" > config/supabase.yml - - - name: Run playbook - shell: bash - run: make render_users_configs - - - name: Setup Pages - uses: actions/configure-pages@v3 - - - name: Upload artifact - uses: actions/upload-pages-artifact@v1 - with: - path: 'static' - - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v2 - - - uses: geekyeggo/delete-artifact@v2 - if: always() - with: - name: github-pages - failOnError: false diff --git a/.github/workflows/CD-production-task.yml b/.github/workflows/CD-production-task.yml index 981cedd..a89328f 100644 --- a/.github/workflows/CD-production-task.yml +++ b/.github/workflows/CD-production-task.yml @@ -65,6 +65,13 @@ jobs: run: | echo "$SUPABASE" > config/supabase.yml + - name: Render FRONTMAN secret into config/frontman.yml + env: + SUPABASE: ${{ secrets.FRONTMAN }} + shell: bash + run: | + echo "$FRONTMAN" > config/frontman.yml + - name: Run playbook shell: bash run: make ${{ inputs.tasks }} diff --git a/.github/workflows/CD-production.yml b/.github/workflows/CD-production.yml index 81586a0..ba9874c 100644 --- a/.github/workflows/CD-production.yml +++ b/.github/workflows/CD-production.yml @@ -10,11 +10,6 @@ on: - master workflow_dispatch: -permissions: - contents: read - pages: write - id-token: write - jobs: deploy-proxies: name: Deploy on production @@ -24,11 +19,8 @@ jobs: task: - deploy_metrics - deploy_proxies + - deploy_frontman with: tasks: ${{ matrix.task }} secrets: inherit - deploy-frontman: - name: Deploy on production - uses: ./.github/workflows/CD-production-frontman.yml - secrets: inherit diff --git a/.gitignore b/.gitignore index 6bcd13a..c396e3b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,8 @@ vault.txt users.csv id_rsa id_rsa.pub -config/users-configs.yml +known_hosts +config/frontman.yml config/metrics.yml config/servers.yml config/supabase.yml diff --git a/Dockerfile b/Dockerfile index ce99d49..26dd5b6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,2 +1,2 @@ FROM alpine/ansible -RUN apk add --no-cache curl make rsync +RUN apk add --no-cache curl make rsync nodejs npm diff --git a/Makefile b/Makefile index 89a337c..bd6fc71 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ USERS_CSV_FILE = users.csv PLAYBOOK_FILE_METRIX = metrics.yml PLAYBOOK_FILE_PROXIES = proxies.yml PLAYBOOK_FILE_USERS_CSV = users-csv.yml -PLAYBOOK_FILE_USERS_CONFIGS = users-configs.yml +PLAYBOOK_FILE_FRONTMAN = frontman.yml HOSTS_FILE = inventory/hosts hosts_encrypt: @@ -11,9 +11,6 @@ hosts_encrypt: hosts_decrypt: ansible-vault decrypt $(HOSTS_FILE) -render_users_configs: - ansible-playbook $(PLAYBOOK_FILE_USERS_CONFIGS) - deploy_frontman: ansible-playbook $(PLAYBOOK_FILE_FRONTMAN) diff --git a/README.md b/README.md index f1f0c3a..830b036 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Stores list of user configs. Users may generate new user configs (my means of HT to render configs for xrays and other components. _The repository with Supabas is not open-source at the moment. It will becore open-source later_. -## GH Pages +## Cloudflare Pages Serves static content: * static html pages with installation instructions which is being developed in a separate repository: [xray-server-frontend](https://github.com/ed-asriyan/xray-server-frontend). The user is provided with a private instruction link @@ -26,7 +26,7 @@ ShadowSocks client each time before connecting to a ShadowSocks server * personal vless [subscription files](https://hiddify.com/app/URL-Scheme) for each client, which is used by Hiddify to refresh list of available servers -Playbook: [users-configs.yml](./users-configs.yml). It just renders files locally, the should be uploaded got GitHub Pages using +Playbook: [frontman.yml](./frontman.yml). It just renders files locally, the should be uploaded got Cloudflare Pages using Actions. ## Metrics diff --git a/config/README.md b/config/README.md index 0c2b850..e93a95b 100644 --- a/config/README.md +++ b/config/README.md @@ -4,7 +4,7 @@ This directory contains configuration files. ## How to edit ### If you use GitHub Actions Create the following secrets: -* `USERS_CONFIGS`: copy content of [users-configs.example.yml](./users-configs.example.yml) to the secret and fill out each variable +* `FRONTMAN`: copy content of [frontman.example.yml](./frontman.example.yml) to the secret and fill out each variable * `SERVERS`: copy content of [servers.example.yml](./servers.example.yml) to the secret and fill out each variable * `HOSTS`: copy content of [hosts.example.yml](./hosts.example.yml) to the secret and fill out each variable * `METRICS`: copy content of [metrics.example.yml](./metrics.example.yml) to the secret and fill out each variable diff --git a/config/frontman.example.yml b/config/frontman.example.yml new file mode 100644 index 0000000..0bfc409 --- /dev/null +++ b/config/frontman.example.yml @@ -0,0 +1,17 @@ +# where redirect to if user opened index page without valid parameters +config_frontman_default_redirect: + +# title users should we when open hiddify +config_users_title: + +# url where frontman is hosted +config_users_base_url: + +# clouflare pages project name +config_frontman_cloudflare_project_name: + +# cloudflare account id +config_frontman_cloudflare_account_id: + +# cloudflare api token with Account -> Cloudflare Pages -> Write permissions. create at https://dash.cloudflare.com/profile/api-tokens +config_frontman_cloudflare_api_token: diff --git a/config/users-configs.example.yml b/config/users-configs.example.yml deleted file mode 100644 index 74d443d..0000000 --- a/config/users-configs.example.yml +++ /dev/null @@ -1,11 +0,0 @@ -# where redirect to if user opened index page without valid parameters -config_users_configs_default_redirect: - -# title users should we when open hiddify -config_users_title: - -# support url. see https://hiddify.com/app/URL-Scheme -config_users_support_url: - -# url where frontman is hosted -config_users_base_url: diff --git a/diagram.svg b/diagram.svg index 47d78e7..59abe06 100644 --- a/diagram.svg +++ b/diagram.svg @@ -1,4 +1,4 @@ -proxy (instance N-2)proxy (instance N-1)1. User opens installation instructionlink (one-time action for user)Static page (index.html) opens in browser1. User opens installation instruction...2. Hiddify pulls list of available serversHiddify in background fetches list ofservers available for the user2. Hiddify pulls list of available servers...proxy (instance N)xrayxraynode-exporternode-export...nginxnginxxray-exporterxray-export...3. Hiddify proxies traffic Hiddify proxies traffic via xraythough one of proxy servers whichconfiguration is provided withinthe previous (2) step3. Hiddify proxies traffic...User generates new configs for friends User generates new configs for friends UserUserSends metricsSends metricsCDCDDeploysproxies.ymlDeploys...AdminAdminMonitorsusageMonitors...DeveloperDevel...Pushesto masterPushes...GitHub ActionsrunnerGitHub Actions...GitHubrepositoryGitHub...Grafana CloudGrafana Cloudcollects metricscollects metricsInternetInternetMakesupdatesMakes...metricsprometheusprometheusGitHub pagesstatic html (instructions)static html (instructions)vless subscription URLsvless subscription URLsDeploysmetrics.ymlDeploys...Supabaseusers' recordsusers' recordsfetchuser recordsfetch...Text is not SVG - cannot display +proxy (instance N-2)proxy (instance N-1)1. User opens installation instructionlink (one-time action for user)Static page (index.html) opens in browser1. User opens installation instruction...2. Hiddify pulls list of available serversHiddify in background fetches list ofservers available for the user2. Hiddify pulls list of available servers...proxy (instance N)xrayxraynode-exporternode-export...nginxnginxxray-exporterxray-export...3. Hiddify proxies traffic Hiddify proxies traffic via xraythough one of proxy servers whichconfiguration is provided withinthe previous (2) step3. Hiddify proxies traffic...User generates new configs for friends User generates new configs for friends UserUserSends metricsSends metricsCDCDDeploysproxies.ymlDeploys...AdminAdminMonitorsusageMonitors...DeveloperDevel...Pushesto masterPushes...GitHub ActionsrunnerGitHub Actions...GitHubrepositoryGitHub...Grafana CloudGrafana Cloudcollects metricscollects metricsInternetInternetMakesupdatesMakes...metricsprometheusprometheusCloudflare pagesstatic html (instructions)static html (instructions)vless subscription URLsvless subscription URLsDeploysmetrics.ymlDeploys...Supabaseusers' recordsusers' recordsfetchuser recordsfetch...Text is not SVG - cannot display diff --git a/users-configs.yml b/frontman.yml similarity index 63% rename from users-configs.yml rename to frontman.yml index 4191202..79488b6 100644 --- a/users-configs.yml +++ b/frontman.yml @@ -2,7 +2,7 @@ - localhost no_log: true vars_files: - - ./config/users-configs.yml + - ./config/frontman.yml - ./config/servers.yml - ./config/supabase.yml pre_tasks: @@ -36,13 +36,15 @@ set_fact: config_local_users: "{{ supabase_users_response.json | map(attribute='child_uuid') | list }}" roles: - - role: users-configs + - role: frontman vars: - users_configs_default_redirect: "{{ config_users_configs_default_redirect }}" - users_configs_servers: "{{ config_servers }}" - users_configs_configs: "{{ config_local_configs }}" - users_configs_users: "{{ config_local_users }}" - users_configs_static_directory_filename: static - users_configs_title: "{{ config_users_title }}" - users_configs_support_url: "{{ config_users_support_url }}" - users_configs_base_url: "{{ config_users_base_url }}" + frontman_default_redirect: "{{ config_frontman_default_redirect }}" + frontman_servers: "{{ config_servers }}" + frontman_configs: "{{ config_local_configs }}" + frontman_users: "{{ config_local_users }}" + frontman_static_directory_filename: static + frontman_title: "{{ config_users_title }}" + frontman_base_url: "{{ config_users_base_url }}" + frontman_cloudflare_project_name: "{{ config_frontman_cloudflare_project_name }}" + frontman_cloudflare_account_id: "{{ config_frontman_cloudflare_account_id }}" + frontman_cloudflare_api_token: "{{ config_frontman_cloudflare_api_token }}" diff --git a/roles/users-configs/README.md b/roles/frontman/README.md similarity index 100% rename from roles/users-configs/README.md rename to roles/frontman/README.md diff --git a/roles/frontman/defaults/main.yml b/roles/frontman/defaults/main.yml new file mode 100644 index 0000000..ccc10a4 --- /dev/null +++ b/roles/frontman/defaults/main.yml @@ -0,0 +1,29 @@ +# where redirect to if user opened index page without paramneters +frontman_default_redirect: + +# users as described in /users-example.yml +frontman_users: + +# files with redirect url +frontman_configs: + +# servers object as described in /servers-example.yml +frontman_servers: + +# relative path of directory where static content should be stored +frontman_static_directory_filename: + +# title users should we when open hiddify +frontman_title: + +# url where frontman is hosted +frontman_base_url: + +# clouflare pages project name +frontman_cloudflare_project_name: + +# cloudflare account id +frontman_cloudflare_account_id: + +# cloudflare api token with Account -> Cloudflare Pages -> Write permissions. create at https://dash.cloudflare.com/profile/api-tokens +frontman_cloudflare_api_token: diff --git a/roles/users-configs/files/robots.txt b/roles/frontman/files/robots.txt similarity index 100% rename from roles/users-configs/files/robots.txt rename to roles/frontman/files/robots.txt diff --git a/roles/frontman/tasks/main.yml b/roles/frontman/tasks/main.yml new file mode 100644 index 0000000..32ce650 --- /dev/null +++ b/roles/frontman/tasks/main.yml @@ -0,0 +1,81 @@ +- name: Ensure required variables are defined + assert: + that: + - frontman_default_redirect is string + - frontman_title is string + - frontman_cloudflare_project_name is string + - frontman_cloudflare_account_id is string + - frontman_cloudflare_api_token is string + +- name: Ensure frontman_users is defined correctly + include_tasks: tasks/assert-users.yml + vars: + users: "{{ frontman_users }}" + +- name: Ensure frontman_configs is defined correctly + include_tasks: tasks/assert-configs.yml + vars: + configs: "{{ frontman_configs }}" + +- name: Ensure frontman_servers is defined correctly + include_tasks: tasks/assert-servers.yml + vars: + servers: "{{ frontman_servers }}" + +- set_fact: + frontman_static_root_local: "./{{ frontman_static_directory_filename }}" + frontman_frontend_name: "{{ lookup('community.general.random_string', length=32, special=False) }}" + +- name: Create static directory locally + file: + path: "{{ frontman_static_root_local }}" + state: directory + +- name: Render index.html + template: + src: index.html.j2 + dest: "{{ frontman_static_root_local }}/index.html" + +- name: Create frontend directory locally + file: + path: "{{ frontman_static_root_local }}/{{ frontman_frontend_name }}" + state: directory + +- name: Render frontend.html + get_url: + url: "{{ frontman_frontend_html_url }}" + dest: "{{ frontman_static_root_local }}/{{ frontman_frontend_name }}/index.html" + +- name: Render frontend config + template: + src: frontend-config.json.j2 + dest: "{{ frontman_static_root_local }}/{{ frontman_frontend_name }}/config.json" + +- name: Copy robots.txt + copy: + src: robots.txt + dest: "{{ frontman_static_root_local }}/robots.txt" + +- name: Render configs + with_items: "{{ frontman_configs }}" + loop_control: + index_var: loop_index + vars: + frontman_config_uuid: "{{ item }}" + include_tasks: render_configs.yml + +- name: Render redirect configs + with_items: "{{ frontman_users }}" + loop_control: + index_var: loop_index + vars: + frontman_user_uuid: "{{ item }}" + template: + src: frontend-link.json.j2 + dest: "{{ frontman_static_root_local }}/{{ frontman_user_uuid }}.json" + +- name: Upload static files to Cloudflare Pages + command: "npx wrangler pages deploy {{ frontman_static_root_local }} --project-name={{ frontman_cloudflare_project_name }}" + environment: + CLOUDFLARE_ACCOUNT_ID: "{{ frontman_cloudflare_account_id }}" + CLOUDFLARE_API_TOKEN: "{{ frontman_cloudflare_api_token }}" diff --git a/roles/frontman/tasks/render_configs.yml b/roles/frontman/tasks/render_configs.yml new file mode 100644 index 0000000..264a84a --- /dev/null +++ b/roles/frontman/tasks/render_configs.yml @@ -0,0 +1,30 @@ + +- name: Create user's directory + file: + path: "{{ frontman_static_root_local }}/{{ frontman_config_uuid }}" + state: directory + +- name: Render hiddify config + template: + src: hiddify.j2 + dest: "{{ frontman_static_root_local }}/{{ frontman_config_uuid }}/{{ frontman_hiddify_filename }}" + +- name: Render hiddify config (index.html for backward compatibility) + template: + src: hiddify.j2 + dest: "{{ frontman_static_root_local }}/{{ frontman_config_uuid }}/index.html" + +- name: Render xray-client config + template: + src: client-xray-config.json.j2 + dest: "{{ frontman_static_root_local }}/{{ frontman_config_uuid }}/{{ frontman_client_xray_config_filename }}" + +- name: Render sock5.Dockerfile + template: + src: socks5.Dockerfile.j2 + dest: "{{ frontman_static_root_local }}/{{ frontman_config_uuid }}/{{ frontman_socks5_docker_filename }}" + +- name: Render urls map + template: + src: urls-map.json.j2 + dest: "{{ frontman_static_root_local }}/{{ frontman_config_uuid }}/{{ frontman_urls_map_filename }}" diff --git a/roles/users-configs/templates/client-xray-config.json.j2 b/roles/frontman/templates/client-xray-config.json.j2 similarity index 77% rename from roles/users-configs/templates/client-xray-config.json.j2 rename to roles/frontman/templates/client-xray-config.json.j2 index 7bf1f07..68406cd 100644 --- a/roles/users-configs/templates/client-xray-config.json.j2 +++ b/roles/frontman/templates/client-xray-config.json.j2 @@ -16,39 +16,39 @@ } ], "outbounds": [ - {% for server_item in users_configs_servers | dict2items %} - {% set users_configs_server_uuid = server_item.key %} - {% set users_configs_server = server_item.value %} + {% for server_item in frontman_servers | dict2items %} + {% set frontman_server_uuid = server_item.key %} + {% set frontman_server = server_item.value %} {% set server_index = loop.index %} - {% for sni, fingerprint in users_configs_server.supported_snis | product(users_configs_server.fingerprints) %} + {% for sni, fingerprint in frontman_server.supported_snis | product(frontman_server.fingerprints) %} { "protocol": "vless", "sendThrough": "0.0.0.0", "settings": { "vnext": [ { - "address": "{{ users_configs_server.host }}", - "port": {{ users_configs_server.port }}, + "address": "{{ frontman_server.host }}", + "port": {{ frontman_server.port }}, "users": [ { - "id": "{{ users_configs_config_uuid }}", + "id": "{{ frontman_config_uuid }}", "encryption": "none", - "flow": "{{ users_configs_server.flow }}", + "flow": "{{ frontman_server.flow }}", "level": 0 } ] } ] }, - "tag": "outbound-vless-{{ server_index }}-{{ loop.index }}-{{ users_configs_server.name | urlencode }}", + "tag": "outbound-vless-{{ server_index }}-{{ loop.index }}-{{ frontman_server.name | urlencode }}", "streamSettings": { "network": "tcp", "security": "reality", "realitySettings": { "fingerprint": "{{ fingerprint }}", "serverName": "{{ sni }}", - "publicKey": "{{ users_configs_server.public_key }}", + "publicKey": "{{ frontman_server.public_key }}", "spiderX": "/&", "shortId": "" } diff --git a/roles/users-configs/templates/frontend-config.json.j2 b/roles/frontman/templates/frontend-config.json.j2 similarity index 100% rename from roles/users-configs/templates/frontend-config.json.j2 rename to roles/frontman/templates/frontend-config.json.j2 diff --git a/roles/frontman/templates/frontend-link.json.j2 b/roles/frontman/templates/frontend-link.json.j2 new file mode 100644 index 0000000..4c3bd17 --- /dev/null +++ b/roles/frontman/templates/frontend-link.json.j2 @@ -0,0 +1,4 @@ +{ + {% set instruction_url = '/' + frontman_frontend_name + '#' + frontman_user_uuid %} + "{{ frontman_instruction_url_field_name }}": "{{ instruction_url }}" +} diff --git a/roles/frontman/templates/hiddify.j2 b/roles/frontman/templates/hiddify.j2 new file mode 100644 index 0000000..b26c284 --- /dev/null +++ b/roles/frontman/templates/hiddify.j2 @@ -0,0 +1,12 @@ +#profile-title: {{ frontman_title }} +#profile-update-interval: 1 + +{% for server_item in frontman_servers | dict2items %} +{% set frontman_server_uuid = server_item.key %} +{% set frontman_server = server_item.value %} +{% set server_index = loop.index %} + +{% for sni, fingerprint in frontman_server.supported_snis | product(frontman_server.fingerprints) %} +vless://{{ frontman_config_uuid }}@{{ frontman_server.host }}:{{ frontman_server.port }}?type=tcp&security=reality&pbk={{ frontman_server.public_key }}&fp={{ fingerprint }}&sni={{ sni }}&spx=%2F&flow={{ frontman_server.flow }}#{{ server_index }}.{{ loop.index }}.%20{{ frontman_server.name | urlencode }} +{% endfor %} +{% endfor %} diff --git a/roles/users-configs/templates/index.html.j2 b/roles/frontman/templates/index.html.j2 similarity index 84% rename from roles/users-configs/templates/index.html.j2 rename to roles/frontman/templates/index.html.j2 index a7cdb9c..437896a 100644 --- a/roles/users-configs/templates/index.html.j2 +++ b/roles/frontman/templates/index.html.j2 @@ -6,7 +6,7 @@