mirror of https://github.com/Aidaho12/haproxy-wi
v8.1.2: Delete letsencrypt.sh script and add LetsEncrypt API endpoints
Remove the letsencrypt.sh script and integrate LetsEncrypt functionality directly into the web application via new API endpoints. This includes creating, updating, retrieving, and deleting LetsEncrypt configurations, improving maintainability and user interaction with the LetsEncrypt feature.pull/401/head
parent
d7f699d376
commit
8ebf934f06
|
@ -12,6 +12,7 @@ from app.views.service.views import (ServiceView, ServiceActionView, ServiceBack
|
|||
ServiceConfigVersionsView, ServiceConfigList)
|
||||
from app.views.service.haproxy_section_views import ListenSectionView, UserListSectionView, PeersSectionView, \
|
||||
GlobalSectionView, DefaultsSectionView
|
||||
from app.views.service.lets_encrypt_views import LetsEncryptsView, LetsEncryptView
|
||||
from app.views.ha.views import HAView, HAVIPView, HAVIPsView
|
||||
from app.views.user.views import UserView, UserGroupView, UserRoles
|
||||
from app.views.udp.views import UDPListener, UDPListeners, UDPListenerActionView
|
||||
|
@ -87,6 +88,8 @@ register_api(GitBackupView, 'backup_git', '/server/backup/git', 'backup_id')
|
|||
bp.add_url_rule('/server/<server_id>/ip', view_func=ServerIPView.as_view('server_ip_ip'), methods=['GET'])
|
||||
bp.add_url_rule('/server/<int:server_id>/ip', view_func=ServerIPView.as_view('server_ip'), methods=['GET'])
|
||||
register_api_for_not_api(CredView, 'cred', '/server/cred', 'cred_id')
|
||||
register_api(LetsEncryptView, 'le_api', '/service/letsencrypt', 'le_id')
|
||||
bp.add_url_rule('service/letsencrypts', view_func=LetsEncryptsView.as_view('les_api'), methods=['GET'])
|
||||
bp.add_url_rule('/server/creds', view_func=CredsView.as_view('creds'), methods=['GET'])
|
||||
bp.add_url_rule('/server/portscanner/<server_id>', view_func=PortScannerView.as_view('port_scanner_ip'), methods=['GET', 'POST'])
|
||||
bp.add_url_rule('/server/portscanner/<int:server_id>', view_func=PortScannerView.as_view('port_scanner'), methods=['GET', 'POST'])
|
||||
|
|
|
@ -135,9 +135,10 @@ def default_values():
|
|||
print(str(e))
|
||||
|
||||
data_source = [
|
||||
{'name': 'superAdmin', 'description': 'Has the highest level of administrative permissions and controls the actions of all other users'},
|
||||
{'name': 'admin', 'description': 'Has access everywhere except the Admin area'},
|
||||
{'name': 'user', 'description': 'Has the same rights as the admin but has no access to the Servers page'},
|
||||
{'name': 'superAdmin',
|
||||
'description': 'Has the highest level of administrative permissions and controls the actions of all other users'},
|
||||
{'name': 'admin', 'description': 'Has admin access to its groups'},
|
||||
{'name': 'user', 'description': 'Has the same rights as the admin but has no access to the Admin area'},
|
||||
{'name': 'guest', 'description': 'Read-only access'}
|
||||
]
|
||||
|
||||
|
@ -680,7 +681,7 @@ def update_db_v_8_1_0_3():
|
|||
|
||||
def update_ver():
|
||||
try:
|
||||
Version.update(version='8.1.0.1').execute()
|
||||
Version.update(version='8.1.2').execute()
|
||||
except Exception:
|
||||
print('Cannot update version')
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ from flask import render_template
|
|||
import app.modules.db.sql as sql
|
||||
import app.modules.db.add as add_sql
|
||||
import app.modules.db.server as server_sql
|
||||
import app.modules.server.ssh as ssh_mod
|
||||
import app.modules.common.common as common
|
||||
import app.modules.config.config as config_mod
|
||||
import app.modules.config.common as config_common
|
||||
|
@ -326,45 +325,6 @@ def get_saved_servers(group: str, term: str) -> dict:
|
|||
return a
|
||||
|
||||
|
||||
def get_le_cert(server_ip: str, lets_domain: str, lets_email: str) -> str:
|
||||
proxy = sql.get_setting('proxy')
|
||||
ssl_path = common.return_nice_path(sql.get_setting('cert_path'), is_service=0)
|
||||
haproxy_dir = sql.get_setting('haproxy_dir')
|
||||
script = "letsencrypt.sh"
|
||||
proxy_serv = ''
|
||||
ssh_settings = ssh_mod.return_ssh_keys_path(server_ip)
|
||||
full_path = '/var/www/haproxy-wi/app'
|
||||
|
||||
os.system(f"cp {full_path}/scripts/{script} {full_path}/{script}")
|
||||
|
||||
if proxy is not None and proxy != '' and proxy != 'None':
|
||||
proxy_serv = proxy
|
||||
|
||||
commands = [
|
||||
f"chmod +x {full_path}/{script} && {full_path}/{script} PROXY={proxy_serv} haproxy_dir={haproxy_dir} DOMAIN={lets_domain} "
|
||||
f"EMAIL={lets_email} SSH_PORT={ssh_settings['port']} SSL_PATH={ssl_path} HOST={server_ip} USER={ssh_settings['user']} "
|
||||
f"PASS='{ssh_settings['password']}' KEY={ssh_settings['key']}"
|
||||
]
|
||||
|
||||
output, error = server_mod.subprocess_execute(commands[0])
|
||||
|
||||
if error:
|
||||
roxywi_common.logging('Roxy-WI server', error, roxywi=1)
|
||||
return error
|
||||
else:
|
||||
for line in output:
|
||||
if any(s in line for s in ("msg", "FAILED")):
|
||||
try:
|
||||
line = line.split(':')[1]
|
||||
line = line.split('"')[1]
|
||||
return line + "<br>"
|
||||
except Exception:
|
||||
return output
|
||||
else:
|
||||
os.remove(f'{full_path}/{script}')
|
||||
return 'success: Certificate has been created'
|
||||
|
||||
|
||||
def get_ssl_cert(server_ip: str, cert_id: int) -> str:
|
||||
cert_path = sql.get_setting('cert_path')
|
||||
command = f"openssl x509 -in {cert_path}/{cert_id} -text"
|
||||
|
|
|
@ -73,9 +73,9 @@ def update_ssh_passphrase(cred_id: int, passphrase: str):
|
|||
out_error(e)
|
||||
|
||||
|
||||
def get_ssh_by_id_and_group(creds_id: int, group_id: int) -> Cred:
|
||||
def get_ssh_by_id_and_group(cred_id: int, group_id: int) -> Cred:
|
||||
try:
|
||||
return Cred.select().where((Cred.group_id == group_id) & (Cred.id == creds_id)).execute()
|
||||
return Cred.select().where((Cred.group_id == group_id) & (Cred.id == cred_id)).execute()
|
||||
except Cred.DoesNotExist:
|
||||
raise RoxywiResourceNotFound
|
||||
except Exception as e:
|
||||
|
|
|
@ -786,6 +786,20 @@ class HaproxySection(BaseModel):
|
|||
constraints = [SQL('UNIQUE (server_id, type, name)')]
|
||||
|
||||
|
||||
class LetsEncrypt(BaseModel):
|
||||
id = AutoField
|
||||
server_id = ForeignKeyField(Server, null=True, on_delete='SET NULL')
|
||||
domains = CharField()
|
||||
email = CharField()
|
||||
api_key = CharField()
|
||||
api_token = CharField()
|
||||
type = CharField()
|
||||
description = CharField()
|
||||
|
||||
class Meta:
|
||||
table_name = 'lets_encrypt'
|
||||
|
||||
|
||||
def create_tables():
|
||||
conn = connect()
|
||||
with conn:
|
||||
|
@ -796,5 +810,5 @@ def create_tables():
|
|||
NginxMetrics, SystemInfo, Services, UserName, GitSetting, CheckerSetting, ApacheMetrics, WafNginx, ServiceStatus,
|
||||
KeepaliveRestart, PD, SmonHistory, SmonAgent, SmonTcpCheck, SmonHttpCheck, SmonPingCheck, SmonDnsCheck, S3Backup,
|
||||
SmonStatusPage, SmonStatusPageCheck, HaCluster, HaClusterSlave, HaClusterVip, HaClusterVirt, HaClusterService,
|
||||
HaClusterRouter, MM, UDPBalancer, HaproxySection]
|
||||
HaClusterRouter, MM, UDPBalancer, HaproxySection, LetsEncrypt]
|
||||
)
|
||||
|
|
|
@ -263,12 +263,14 @@ def delete_ha_virt(vip_id: int) -> None:
|
|||
pass
|
||||
|
||||
|
||||
def check_ha_virt(vip_id: int) -> bool:
|
||||
def check_ha_virt(vip_id: int) -> int:
|
||||
try:
|
||||
HaClusterVirt.get(HaClusterVirt.vip_id == vip_id).virt_id
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
_ = HaClusterVirt.get(HaClusterVirt.vip_id == vip_id).virt_id
|
||||
return 1
|
||||
except HaClusterVirt.DoesNotExist:
|
||||
return 0
|
||||
except Exception as e:
|
||||
out_error(e)
|
||||
|
||||
|
||||
def select_ha_virts(cluster_id: int) -> HaClusterVirt:
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
from app.modules.db.db_model import LetsEncrypt, Server
|
||||
from app.modules.db.common import out_error
|
||||
from app.modules.roxywi.exception import RoxywiResourceNotFound
|
||||
|
||||
|
||||
def get_le(le_id: int) -> LetsEncrypt:
|
||||
try:
|
||||
return LetsEncrypt.get(LetsEncrypt.id == le_id)
|
||||
except LetsEncrypt.DoesNotExist:
|
||||
raise RoxywiResourceNotFound
|
||||
except Exception as e:
|
||||
out_error(e)
|
||||
|
||||
|
||||
def get_le_with_group(le_id: int, group_id: int) -> LetsEncrypt:
|
||||
try:
|
||||
return LetsEncrypt.select().join(Server).where(
|
||||
(LetsEncrypt.id == le_id) &
|
||||
(Server.group_id == group_id)
|
||||
).get()
|
||||
except LetsEncrypt.DoesNotExist:
|
||||
raise RoxywiResourceNotFound
|
||||
except Exception as e:
|
||||
out_error(e)
|
||||
|
||||
|
||||
def select_le_with_group(group_id: int) -> LetsEncrypt:
|
||||
try:
|
||||
return LetsEncrypt.select().join(Server).where(Server.group_id == group_id).execute()
|
||||
except Exception as e:
|
||||
out_error(e)
|
||||
|
||||
|
||||
def insert_le(**kwargs) -> int:
|
||||
try:
|
||||
return LetsEncrypt.insert(**kwargs).execute()
|
||||
except Exception as e:
|
||||
out_error(e)
|
||||
|
||||
|
||||
def update_le(le_id: int, **kwargs) -> int:
|
||||
try:
|
||||
return LetsEncrypt.update(**kwargs).where(LetsEncrypt.id == le_id).execute()
|
||||
except LetsEncrypt.DoesNotExist:
|
||||
raise RoxywiResourceNotFound
|
||||
except Exception as e:
|
||||
out_error(e)
|
||||
|
||||
|
||||
def delete_le(le_id: int) -> None:
|
||||
try:
|
||||
LetsEncrypt.delete().where(LetsEncrypt.id == le_id).execute()
|
||||
except Exception as e:
|
||||
out_error(e)
|
|
@ -4,9 +4,10 @@ from typing import Optional, Annotated, Union, Literal, Any, Dict, List
|
|||
|
||||
from shlex import quote
|
||||
from pydantic_core import CoreSchema, core_schema
|
||||
from pydantic import BaseModel, Base64Str, StringConstraints, IPvAnyAddress, GetCoreSchemaHandler, AnyUrl
|
||||
from pydantic import BaseModel, Base64Str, StringConstraints, IPvAnyAddress, GetCoreSchemaHandler, AnyUrl, root_validator, EmailStr
|
||||
|
||||
DomainName = Annotated[str, StringConstraints(pattern=r"^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z0-9-]{0,61}[a-z0-9]$")]
|
||||
WildcardDomainName = Annotated[str, StringConstraints(pattern=r"^(?:[a-z0-9\*](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z0-9-]{0,61}[a-z0-9]$")]
|
||||
|
||||
|
||||
class EscapedString(str):
|
||||
|
@ -134,6 +135,7 @@ class ServerRequest(BaseModel):
|
|||
|
||||
class GroupQuery(BaseModel):
|
||||
group_id: Optional[int] = None
|
||||
recurse: Optional[bool] = False
|
||||
|
||||
|
||||
class GroupRequest(BaseModel):
|
||||
|
@ -301,6 +303,52 @@ class SavedServerRequest(BaseModel):
|
|||
description: Optional[EscapedString] = None
|
||||
|
||||
|
||||
class LetsEncryptRequest(BaseModel):
|
||||
server_id: int
|
||||
domains: List[WildcardDomainName]
|
||||
email: Optional[EmailStr] = None
|
||||
type: Literal['standalone', 'route53', 'cloudflare', 'digitalocean', 'linode']
|
||||
api_key: Optional[EscapedString] = None
|
||||
api_token: EscapedString
|
||||
description: Optional[EscapedString] = None
|
||||
|
||||
@root_validator(pre=True)
|
||||
@classmethod
|
||||
def is_email_when_standalone(cls, values):
|
||||
cert_type = ''
|
||||
email = ''
|
||||
if 'type' in values:
|
||||
cert_type = values['type']
|
||||
if 'email' in values:
|
||||
email = values['email']
|
||||
if cert_type == 'standalone' and email == '':
|
||||
raise ValueError('Email must be when type is standalone')
|
||||
return values
|
||||
|
||||
@root_validator(pre=True)
|
||||
@classmethod
|
||||
def is_api_key_when_route53(cls, values):
|
||||
cert_type = ''
|
||||
api_key = ''
|
||||
if 'type' in values:
|
||||
cert_type = values['type']
|
||||
if 'api_key' in values:
|
||||
api_key = values['api_key']
|
||||
if cert_type == 'route53' and api_key == '':
|
||||
raise ValueError('api_key(secret key) must be when type is route53')
|
||||
return values
|
||||
|
||||
|
||||
class LetsEncryptDeleteRequest(BaseModel):
|
||||
server_id: int
|
||||
domains: List[WildcardDomainName]
|
||||
email: Optional[str] = None
|
||||
type: Literal['standalone', 'route53', 'cloudflare', 'digitalocean', 'linode']
|
||||
api_key: Optional[EscapedString] = None
|
||||
api_token: EscapedString
|
||||
description: Optional[EscapedString] = None
|
||||
|
||||
|
||||
class HaproxyBinds(BaseModel):
|
||||
ip: Optional[str] = None
|
||||
port: Annotated[int, Gt(1), Le(65535)]
|
||||
|
|
|
@ -12,7 +12,6 @@ import app.modules.db.history as history_sql
|
|||
import app.modules.db.portscanner as ps_sql
|
||||
import app.modules.server.ssh as mod_ssh
|
||||
import app.modules.common.common as common
|
||||
import app.modules.roxywi.auth as roxywi_auth
|
||||
import app.modules.roxywi.common as roxywi_common
|
||||
|
||||
|
||||
|
|
|
@ -51,8 +51,8 @@ def return_ssh_keys_path(server_ip: str, cred_id: int = None) -> dict:
|
|||
ssh_settings.setdefault('passphrase', passphrase)
|
||||
|
||||
try:
|
||||
ssh_port = [str(server[10]) for server in server_sql.select_servers(server=server_ip)]
|
||||
ssh_settings.setdefault('port', ssh_port[0])
|
||||
server = server_sql.get_server_by_ip(server_ip)
|
||||
ssh_settings.setdefault('port', server.port)
|
||||
except Exception as e:
|
||||
raise Exception(f'error: Cannot get SSH port: {e}')
|
||||
|
||||
|
@ -242,8 +242,6 @@ def get_creds(group_id: int = None, cred_id: int = None, not_shared: bool = Fals
|
|||
def _return_correct_ssh_file(cred: CredRequest) -> str:
|
||||
lib_path = get_config.get_config_var('main', 'lib_path')
|
||||
group_name = group_sql.get_group_name_by_id(cred.group_id)
|
||||
# if not cred.key_enabled:
|
||||
# return ''
|
||||
if group_name not in cred.name:
|
||||
return f'{lib_path}/keys/{cred.name}_{group_name}.pem'
|
||||
else:
|
||||
|
|
|
@ -193,7 +193,7 @@ def insert_vip(cluster_id: int, cluster: HAClusterVIP, group_id: int) -> int:
|
|||
raise Exception(f'Cannot get servers: {e}')
|
||||
|
||||
try:
|
||||
vip_id = HaClusterVip.insert(cluster_id=cluster_id, router_id=router_id, vip=vip, return_master=cluster.return_master).execute()
|
||||
vip_id = HaClusterVip.insert(cluster_id=cluster_id, router_id=router_id, vip=vip, use_src=cluster.use_src, return_master=cluster.return_master).execute()
|
||||
except Exception as e:
|
||||
raise Exception(f'error: Cannot save VIP {vip}: {e}')
|
||||
|
||||
|
|
|
@ -230,7 +230,7 @@ def generate_service_inv(json_data: ServiceInstall, installed_service: str) -> o
|
|||
|
||||
def run_ansible(inv: dict, server_ips: list, ansible_role: str) -> dict:
|
||||
inventory_path = '/var/www/haproxy-wi/app/scripts/ansible/inventory'
|
||||
inventory = f'{inventory_path}/{ansible_role}.json'
|
||||
inventory = f'{inventory_path}/{ansible_role}-{random.randint(0, 35)}.json'
|
||||
proxy = sql.get_setting('proxy')
|
||||
proxy_serv = ''
|
||||
tags = ''
|
||||
|
@ -330,8 +330,13 @@ def run_ansible(inv: dict, server_ips: list, ansible_role: str) -> dict:
|
|||
def run_ansible_locally(inv: dict, ansible_role: str) -> dict:
|
||||
inventory_path = '/var/www/haproxy-wi/app/scripts/ansible/inventory'
|
||||
inventory = f'{inventory_path}/{ansible_role}-{random.randint(0, 35)}.json'
|
||||
# proxy = sql.get_setting('proxy')
|
||||
# proxy_serv = ''
|
||||
proxy_serv = ''
|
||||
proxy = sql.get_setting('proxy')
|
||||
|
||||
if proxy is not None and proxy != '' and proxy != 'None':
|
||||
proxy_serv = proxy
|
||||
|
||||
inv['server']['hosts']['localhost']['PROXY'] = proxy_serv
|
||||
|
||||
envvars = {
|
||||
'ANSIBLE_DISPLAY_OK_HOSTS': 'no',
|
||||
|
|
|
@ -284,15 +284,6 @@ def create_map():
|
|||
return add_mod.edit_map(map_name, group)
|
||||
|
||||
|
||||
@bp.post('lets')
|
||||
def lets():
|
||||
server_ip = common.checkAjaxInput(request.form.get('serv'))
|
||||
lets_domain = common.checkAjaxInput(request.form.get('lets_domain'))
|
||||
lets_email = common.checkAjaxInput(request.form.get('lets_email'))
|
||||
|
||||
return add_mod.get_le_cert(server_ip, lets_domain, lets_email)
|
||||
|
||||
|
||||
@bp.post('/nginx/upstream')
|
||||
@get_user_params()
|
||||
def add_nginx_upstream():
|
||||
|
|
|
@ -17,7 +17,7 @@ import app.modules.service.common as service_common
|
|||
import app.modules.roxywi.common as roxywi_common
|
||||
import app.modules.roxywi.overview as roxy_overview
|
||||
from app.views.service.views import ServiceActionView, ServiceBackendView, ServiceView
|
||||
|
||||
from app.views.service.lets_encrypt_views import LetsEncryptView, LetsEncryptsView
|
||||
|
||||
bp.add_url_rule('/<service>/<server_id>/<any(start, stop, reload, restart):action>', view_func=ServiceActionView.as_view('service_action_ip'), methods=['GET'])
|
||||
bp.add_url_rule('/<service>/<int:server_id>/<any(start, stop, reload, restart):action>', view_func=ServiceActionView.as_view('service_action'), methods=['GET'])
|
||||
|
@ -25,6 +25,9 @@ bp.add_url_rule('/<service>/<server_id>/backend', view_func=ServiceBackendView.a
|
|||
bp.add_url_rule('/<service>/<int:server_id>/backend', view_func=ServiceBackendView.as_view('service_backend'), methods=['GET'])
|
||||
bp.add_url_rule('/<service>/<server_id>/status', view_func=ServiceView.as_view('service_ip'), methods=['GET'])
|
||||
bp.add_url_rule('/<service>/<int:server_id>/status', view_func=ServiceView.as_view('service'), methods=['GET'])
|
||||
bp.add_url_rule('/letsencrypt', view_func=LetsEncryptView.as_view('le_web'), methods=['POST'])
|
||||
bp.add_url_rule('/letsencrypt/<int:le_id>', view_func=LetsEncryptView.as_view('le_web_id'), methods=['GET', 'PUT', 'DELETE'])
|
||||
bp.add_url_rule('/letsencrypts', view_func=LetsEncryptsView.as_view('le_webs'), methods=['GET'])
|
||||
|
||||
|
||||
@bp.before_request
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
cron:
|
||||
name: "Roxy-WI Backup configs for server {{ SERVER }} {{ item }}"
|
||||
special_time: "{{ TIME }}"
|
||||
job: "rsync -arv {{ TYPE }} /var/lib/roxy-wi/configs/{{ item }}/{{ SERVER }}* {{ USER }}@{{ HOST }}:{{ RPATH }}/roxy-wi-configs-backup/configs/{{ item }} -e 'ssh -i {{ KEY }} -o StrictHostKeyChecking=no' --log-file=/var/www/haproxy-wi/log/backup.log"
|
||||
job: "rsync -arv {{ TYPE }} /var/lib/roxy-wi/configs/{{ item }}/{{ SERVER }}* {{ USER }}@{{ HOST }}:{{ RPATH }}/roxy-wi-configs-backup/configs/{{ item }} -e 'ssh -i {{ KEY }} -o StrictHostKeyChecking=no' --log-file=/var/www/roxy-wi/log/backup.log"
|
||||
when: not DELJOB
|
||||
delegate_to: localhost
|
||||
with_items:
|
||||
|
|
|
@ -1,56 +1,9 @@
|
|||
- hosts: "{{ variable_host }}"
|
||||
---
|
||||
- name: Obtain Lets Encrypt certificate
|
||||
hosts: localhost
|
||||
connection: local
|
||||
become: yes
|
||||
become_method: sudo
|
||||
tasks:
|
||||
|
||||
- name: install EPEL Repository
|
||||
yum:
|
||||
name: epel-release
|
||||
state: latest
|
||||
when: (ansible_facts['os_family'] == "RedHat" or ansible_facts['os_family'] == 'CentOS')
|
||||
ignore_errors: yes
|
||||
failed_when: false
|
||||
no_log: True
|
||||
environment:
|
||||
http_proxy: "{{PROXY}}"
|
||||
https_proxy: "{{PROXY}}"
|
||||
|
||||
- name: Install certbot
|
||||
package:
|
||||
name: certbot
|
||||
state: present
|
||||
environment:
|
||||
http_proxy: "{{PROXY}}"
|
||||
https_proxy: "{{PROXY}}"
|
||||
|
||||
- name: Kill cerbot standalone
|
||||
shell: ps ax |grep 'certbot certonly --standalone' |grep -v grep |awk '{print $1}' |xargs kill
|
||||
ignore_errors: yes
|
||||
failed_when: false
|
||||
no_log: True
|
||||
|
||||
- name: Get cert
|
||||
command: certbot certonly --standalone -d "{{DOMAIN}}" --non-interactive --agree-tos --email "{{EMAIL}}" --http-01-port=8888
|
||||
|
||||
- name: Combine into pem file
|
||||
shell: cat /etc/letsencrypt/live/{{DOMAIN}}/fullchain.pem /etc/letsencrypt/live/{{DOMAIN}}/privkey.pem > "{{SSL_PATH}}"/"{{DOMAIN}}".pem
|
||||
|
||||
- name: Creates directory
|
||||
file:
|
||||
path: "{{haproxy_dir}}/scripts"
|
||||
state: directory
|
||||
|
||||
- name: Copy renew script
|
||||
template:
|
||||
src: /var/www/haproxy-wi/app/scripts/ansible/roles/renew_letsencrypt.j2
|
||||
dest: "{{haproxy_dir}}/scripts/renew_letsencrypt.sh"
|
||||
mode: '0755'
|
||||
ignore_errors: yes
|
||||
failed_when: false
|
||||
no_log: True
|
||||
|
||||
- name: Creates cron jobs
|
||||
cron:
|
||||
name: "Let's encrypt renew script"
|
||||
special_time: "monthly"
|
||||
job: '{{haproxy_dir}}/scripts/renew_letsencrypt.sh'
|
||||
gather_facts: yes
|
||||
roles:
|
||||
- role: letsencrypt
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
- name: Delete RSYNC job
|
||||
cron:
|
||||
name: "Roxy-WI le certificate {{ main_domain }} {{ item.key }}"
|
||||
special_time: monthly
|
||||
state: absent
|
||||
job: "rsync -arv /etc/letsencrypt/live/{{main_domain}}/* {{ item.value.split('@')[0] }}@{{ item.key }}:{{ ssl_path }} -e 'ssh -i {{ item.value.split('@')[1] }} -o StrictHostKeyChecking=no' --log-file=/var/www/roxy-wi/log/letsencrypt.log"
|
||||
loop: "{{ servers | dict2items }}"
|
||||
|
||||
- name: Delete DNS secret file
|
||||
file:
|
||||
path: "~/.secrets/certbot/{{ cert_type }}-{{ main_domain }}.ini"
|
||||
state: absent
|
|
@ -0,0 +1,125 @@
|
|||
---
|
||||
- name: Kill cerbot standalone
|
||||
shell: ps ax |grep 'certbot certonly --standalone' |grep -v grep |awk '{print $1}' |xargs kill
|
||||
ignore_errors: yes
|
||||
failed_when: false
|
||||
no_log: True
|
||||
|
||||
- name: Creates certbot directory
|
||||
file:
|
||||
path: ~/.secrets/certbot/
|
||||
state: directory
|
||||
|
||||
- name: Install Standalone
|
||||
when: cert_type == "standalone"
|
||||
block:
|
||||
- name: install EPEL Repository
|
||||
yum:
|
||||
name: epel-release
|
||||
state: latest
|
||||
when: (ansible_facts['os_family'] == "RedHat" or ansible_facts['os_family'] == 'CentOS')
|
||||
ignore_errors: yes
|
||||
failed_when: false
|
||||
no_log: True
|
||||
environment:
|
||||
http_proxy: "{{PROXY}}"
|
||||
https_proxy: "{{PROXY}}"
|
||||
|
||||
- name: Install certbot
|
||||
package:
|
||||
name: certbot
|
||||
state: present
|
||||
environment:
|
||||
http_proxy: "{{PROXY}}"
|
||||
https_proxy: "{{PROXY}}"
|
||||
|
||||
- name: Get cert
|
||||
command: certbot certonly --standalone "{{domains_command}}" --non-interactive --agree-tos --email "{{email}}" --http-01-port=8888
|
||||
|
||||
- name: Combine into pem file
|
||||
shell: "cat /etc/letsencrypt/live/{{main_domain}}/fullchain.pem /etc/letsencrypt/live/{{main_domain}}/privkey.pem > {{ssl_path}}/{{main_domain}}.pem"
|
||||
|
||||
- name: Creates directory
|
||||
file:
|
||||
path: "{{haproxy_dir}}/scripts"
|
||||
state: directory
|
||||
|
||||
- name: Copy renew script
|
||||
template:
|
||||
src: renew_letsencrypt.j2
|
||||
dest: "{{haproxy_dir}}/scripts/renew_letsencrypt.sh"
|
||||
mode: '0755'
|
||||
ignore_errors: yes
|
||||
failed_when: false
|
||||
no_log: True
|
||||
|
||||
- name: Creates cron jobs
|
||||
cron:
|
||||
name: "Let's encrypt renew script"
|
||||
special_time: "monthly"
|
||||
job: '{{haproxy_dir}}/scripts/renew_letsencrypt.sh'
|
||||
|
||||
- name: Install DNS cert
|
||||
when: cert_type != "standalone"
|
||||
block:
|
||||
- name: install EPEL Repository
|
||||
yum:
|
||||
name: epel-release
|
||||
state: latest
|
||||
when: (ansible_facts['os_family'] == "RedHat" or ansible_facts['os_family'] == 'CentOS')
|
||||
ignore_errors: yes
|
||||
failed_when: false
|
||||
no_log: True
|
||||
environment:
|
||||
http_proxy: "{{PROXY}}"
|
||||
https_proxy: "{{PROXY}}"
|
||||
|
||||
- name: Install certbot
|
||||
package:
|
||||
name: certbot
|
||||
state: present
|
||||
environment:
|
||||
http_proxy: "{{PROXY}}"
|
||||
https_proxy: "{{PROXY}}"
|
||||
|
||||
- name: Install cert bot plugin
|
||||
pip:
|
||||
name: "certbot-dns-{{ cert_type }}"
|
||||
executable: /usr/local/bin/pip3
|
||||
state: latest
|
||||
|
||||
- name: Copy DNS secret file
|
||||
template:
|
||||
src: "{{ cert_type }}.j2"
|
||||
dest: "~/.secrets/certbot/{{ cert_type }}-{{ main_domain }}.ini"
|
||||
|
||||
- name: Obtain certificate
|
||||
shell: "certbot certonly --dns-{{ cert_type }} {{domains_command}} --dns-{{ cert_type }}-credentials ~/.secrets/certbot/{{ cert_type }}-{{ main_domain }}.ini --dns-{{ cert_type }}-propagation-seconds 60"
|
||||
environment:
|
||||
AWS_CONFIG_FILE: "~/.secrets/certbot/{{ cert_type }}.ini"
|
||||
|
||||
# - name: Obtain certificate
|
||||
# shell: "touch /etc/letsencrypt/live/{{main_domain}}/fullchain.pem & touch /etc/letsencrypt/live/{{main_domain}}/privkey.pem"
|
||||
# environment:
|
||||
# AWS_CONFIG_FILE: "~/.secrets/certbot/{{ cert_type }}.ini"
|
||||
|
||||
- name: Combine into pem file
|
||||
shell: cat /etc/letsencrypt/live/{{main_domain}}/fullchain.pem /etc/letsencrypt/live/{{main_domain}}/privkey.pem > /etc/letsencrypt/live/{{main_domain}}/{{main_domain}}.pem
|
||||
|
||||
- name: Copy certificate
|
||||
shell: "scp -o StrictHostKeyChecking=no -i {{ item.value.split('@')[1] }} /etc/letsencrypt/live/{{main_domain}}/* {{ item.value.split('@')[0] }}@{{ item.key }}:{{ ssl_path }}"
|
||||
loop: "{{ servers | dict2items }}"
|
||||
|
||||
- name: Create certbot certificate renew job
|
||||
cron:
|
||||
name: "Roxy-WI certbot certificate renew"
|
||||
minute: "0"
|
||||
hour: "0,12"
|
||||
job: "root /opt/certbot/bin/python -c 'import random; import time; time.sleep(random.random() * 3600)' && sudo certbot renew -q"
|
||||
|
||||
- name: Create RSYNC job
|
||||
cron:
|
||||
name: "Roxy-WI le certificate {{ main_domain }} {{ item.key }}"
|
||||
special_time: monthly
|
||||
job: "rsync -arv /etc/letsencrypt/live/{{main_domain}}/* {{ item.value.split('@')[0] }}@{{ item.key }}:{{ ssl_path }} -e 'ssh -i {{ item.value.split('@')[1] }} -o StrictHostKeyChecking=no' --log-file=/var/www/roxy-wi/log/letsencrypt.log"
|
||||
loop: "{{ servers | dict2items }}"
|
|
@ -0,0 +1 @@
|
|||
- include_tasks: "{{ action }}.yml"
|
|
@ -0,0 +1,2 @@
|
|||
# Cloudflare API token used by Certbot
|
||||
dns_cloudflare_api_token = {{ token }}
|
|
@ -0,0 +1,2 @@
|
|||
# DigitalOcean API credentials used by Certbot
|
||||
dns_digitalocean_token = {{ token }}
|
|
@ -0,0 +1,3 @@
|
|||
# Linode API credentials used by Certbot
|
||||
dns_linode_key = {{ token }}
|
||||
dns_linode_version = 4
|
|
@ -20,4 +20,4 @@ for i in $(ls -d */ |awk -F"/" '{print $1}'); do
|
|||
done
|
||||
|
||||
# Reload HAProxy
|
||||
sudo systemctl reload haproxy
|
||||
sudo systemctl reload haproxy
|
|
@ -0,0 +1,3 @@
|
|||
[default]
|
||||
aws_access_key_id={{ secret_key }}
|
||||
aws_secret_access_key={{ token }}
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
- name: Obtain Lets Encrypt certificate
|
||||
hosts: all
|
||||
connection: local
|
||||
become: yes
|
||||
become_method: sudo
|
||||
gather_facts: yes
|
||||
roles:
|
||||
- role: letsencrypt
|
||||
environment:
|
||||
http_proxy: "{{PROXY}}"
|
||||
https_proxy: "{{PROXY}}"
|
|
@ -1,44 +0,0 @@
|
|||
#!/bin/bash
|
||||
for ARGUMENT in "$@"
|
||||
do
|
||||
KEY=$(echo $ARGUMENT | cut -f1 -d=)
|
||||
VALUE=$(echo $ARGUMENT | cut -f2 -d=)
|
||||
|
||||
case "$KEY" in
|
||||
PROXY) PROXY=${VALUE} ;;
|
||||
HOST) HOST=${VALUE} ;;
|
||||
USER) USER=${VALUE} ;;
|
||||
PASS) PASS=${VALUE} ;;
|
||||
KEY) KEY=${VALUE} ;;
|
||||
SSH_PORT) SSH_PORT=${VALUE} ;;
|
||||
DOMAIN) DOMAIN=${VALUE} ;;
|
||||
EMAIL) EMAIL=${VALUE} ;;
|
||||
SSL_PATH) SSL_PATH=${VALUE} ;;
|
||||
haproxy_dir) haproxy_dir=${VALUE} ;;
|
||||
*)
|
||||
esac
|
||||
done
|
||||
|
||||
export ANSIBLE_HOST_KEY_CHECKING=False
|
||||
export ANSIBLE_DISPLAY_SKIPPED_HOSTS=False
|
||||
export ACTION_WARNINGS=False
|
||||
export LOCALHOST_WARNING=False
|
||||
export COMMAND_WARNINGS=False
|
||||
|
||||
PWD=/var/www/haproxy-wi/app/scripts/ansible/
|
||||
echo "$HOST ansible_port=$SSH_PORT" > $PWD/$HOST
|
||||
|
||||
if [[ $KEY == "" ]]; then
|
||||
ansible-playbook $PWD/roles/letsencrypt.yml -e "ansible_user=$USER ansible_ssh_pass=$PASS ansible_port=$SSH_PORT variable_host=$HOST PROXY=$PROXY DOMAIN=$DOMAIN EMAIL=$EMAIL haproxy_dir=$haproxy_dir SSL_PATH=$SSL_PATH" -i $PWD/$HOST
|
||||
else
|
||||
ansible-playbook $PWD/roles/letsencrypt.yml --key-file $KEY -e "ansible_user=$USER ansible_port=$SSH_PORT variable_host=$HOST PROXY=$PROXY DOMAIN=$DOMAIN EMAIL=$EMAIL haproxy_dir=$haproxy_dir SSL_PATH=$SSL_PATH" -i $PWD/$HOST
|
||||
fi
|
||||
|
||||
if [ $? -gt 0 ]
|
||||
then
|
||||
echo "error: Can't create SSL certificate"
|
||||
exit 1
|
||||
else
|
||||
echo "ok"
|
||||
fi
|
||||
rm -f $PWD/$HOST
|
|
@ -1,6 +1,25 @@
|
|||
window.onload = function() {
|
||||
var cur_url = window.location.href.split('/').pop();
|
||||
let activeTabIdx = $('#tabs').tabs('option','active');
|
||||
if (cur_url.split('#')[1] === 'ssl') {
|
||||
if (activeTabIdx === 4) {
|
||||
getLes();
|
||||
}
|
||||
}
|
||||
}
|
||||
$( function() {
|
||||
$("#tabs ul li").click(function () {
|
||||
let activeTab = $(this).find("a").attr("href");
|
||||
let activeTabClass = activeTab.replace('#', '');
|
||||
$('.menu li ul li').each(function () {
|
||||
activeSubMenu($(this), activeTabClass)
|
||||
});
|
||||
if (activeTab === '#ssl') {
|
||||
getLes();
|
||||
}
|
||||
});
|
||||
$("#listen-mode-select").on('selectmenuchange', function () {
|
||||
if ($("#listen-mode-select option:selected").val() == "tcp") {
|
||||
if ($("#listen-mode-select option:selected").val() === "tcp") {
|
||||
$("#https-listen-span").hide("fast");
|
||||
$("#https-hide-listen").hide("fast");
|
||||
$("#compression").checkboxradio("disable");
|
||||
|
@ -19,7 +38,7 @@ $( function() {
|
|||
}
|
||||
});
|
||||
$("#frontend-mode-select").on('selectmenuchange', function () {
|
||||
if ($("#frontend-mode-select option:selected").val() == "tcp") {
|
||||
if ($("#frontend-mode-select option:selected").val() === "tcp") {
|
||||
$("#https-frontend-span").hide("fast");
|
||||
$("#https-hide-frontend").hide("fast");
|
||||
$("#compression2").checkboxradio("disable");
|
||||
|
@ -442,6 +461,7 @@ $( function() {
|
|||
$(this).children("#add3").css('border-left', '4px solid #5D9CEB');
|
||||
$(this).children("#add3").css('background-color', 'var(--right-menu-blue-rolor)');
|
||||
});
|
||||
getLes();
|
||||
$("#tabs").tabs("option", "active", 4);
|
||||
});
|
||||
$("#add4").on("click", function () {
|
||||
|
@ -530,40 +550,6 @@ $( function() {
|
|||
}
|
||||
});
|
||||
});
|
||||
$('#lets_button').click(function () {
|
||||
let lets_domain = $('#lets_domain').val();
|
||||
let lets_email = $('#lets_email').val();
|
||||
if (lets_email == '' || lets_domain == '') {
|
||||
toastr.error('Fields cannot be empty');
|
||||
} else if (validateEmail(lets_email)) {
|
||||
$("#ajax-ssl").html(wait_mess);
|
||||
$.ajax({
|
||||
url: "/add/lets",
|
||||
data: {
|
||||
serv: $('#serv_for_lets').val(),
|
||||
lets_domain: lets_domain,
|
||||
lets_email: lets_email
|
||||
},
|
||||
type: "POST",
|
||||
success: function (data) {
|
||||
if (data.indexOf('error:') != '-1' || data.indexOf('ERROR') != '-1' || data.indexOf('FAILED') != '-1') {
|
||||
toastr.clear();
|
||||
toastr.error(data);
|
||||
} else if (data.indexOf('WARNING') != '-1') {
|
||||
toastr.clear();
|
||||
toastr.warning(data);
|
||||
} else {
|
||||
toastr.clear();
|
||||
toastr.success(data);
|
||||
}
|
||||
$("#ajax-ssl").html('');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
toastr.clear();
|
||||
toastr.error('Wrong e-mail format');
|
||||
}
|
||||
});
|
||||
$('[name=add-server-input]').click(function () {
|
||||
$("[name=add_servers]").append(add_server_var);
|
||||
changePortCheckFromServerPort();
|
||||
|
|
|
@ -0,0 +1,222 @@
|
|||
let provides = {'standalone': "Stand alone", 'route53': 'Route53', 'linode': 'Linode', 'cloudflare': 'Cloudflare', 'digitalocean': 'Digitalocean'};
|
||||
$( function() {
|
||||
let typeSelect = $( "#new-le-type" );
|
||||
typeSelect.on('selectmenuchange',function() {
|
||||
if (typeSelect.val() === 'standalone') {
|
||||
$('.le-standalone').show();
|
||||
$('.le-dns').hide();
|
||||
$('.le-aws').hide();
|
||||
} else if (typeSelect.val() === 'cloudflare' || typeSelect.val() === 'digitalocean' || typeSelect.val() === 'linode') {
|
||||
$('.le-standalone').hide();
|
||||
$('.le-dns').show();
|
||||
$('.le-aws').hide();
|
||||
} else if (typeSelect.val() === 'route53' ) {
|
||||
$('.le-standalone').hide();
|
||||
$('.le-dns').hide();
|
||||
$('.le-aws').show();
|
||||
}
|
||||
});
|
||||
});
|
||||
function addLe(dialogId) {
|
||||
let domain = $('#new-le-domain').val();
|
||||
let email = $('#new-le-email').val();
|
||||
let type = $('#new-le-type').val();
|
||||
let api_key = '';
|
||||
let api_token = $('#new-le-token').val();
|
||||
let valid = true;
|
||||
let allFields = '';
|
||||
if (type === 'standalone') {
|
||||
allFields = $([]).add($('#new-le-domain')).add($('#new-le-email'));
|
||||
allFields.removeClass("ui-state-error");
|
||||
valid = valid && checkLength($('#new-le-email'), "Email", 1);
|
||||
}
|
||||
if (type === 'cloudflare' || type === 'digitalocean' || type === 'linode') {
|
||||
allFields = $([]).add($('#new-le-domain')).add($('#new-le-token'));
|
||||
allFields.removeClass("ui-state-error");
|
||||
valid = valid && checkLength($('#new-le-token'), "Token", 1);
|
||||
}
|
||||
if (type === 'route53') {
|
||||
allFields = $([]).add($('#new-le-domain')).add($('#new-le-access_key_id')).add($('#new-le-secret_access_key'));
|
||||
allFields.removeClass("ui-state-error");
|
||||
valid = valid && checkLength($('#new-le-access_key_id'), "Access key ID", 1);
|
||||
valid = valid && checkLength($('#new-le-secret_access_key'), "Access key", 1);
|
||||
}
|
||||
valid = valid && checkLength($('#new-le-domain'), "Domains", 1);
|
||||
if ($('#new-le-server_id').val() === '------' || $('#new-le-server_id').val() === null) {
|
||||
toastr.warning('Select server firts')
|
||||
return false;
|
||||
}
|
||||
if (!valid) {
|
||||
return false;
|
||||
}
|
||||
if (type === 'standalone') {
|
||||
if (!validateEmail(email)) {
|
||||
toastr.warning('Invalid email format');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (type === 'route53') {
|
||||
api_key = $('#new-le-access_key_id').val();
|
||||
api_token = $('#new-le-secret_access_key').val();
|
||||
}
|
||||
let domains = [];
|
||||
if (domain.includes(',')) {
|
||||
domains = domain.split(',').filter(function (item) {
|
||||
return item.trim() !== '';
|
||||
});
|
||||
} else if (domain.includes(' ')) {
|
||||
domains = domain.split(' ').filter(function (item) {
|
||||
return item.trim() !== '';
|
||||
});
|
||||
} else {
|
||||
domains.push(domain);
|
||||
}
|
||||
let jsonData = {
|
||||
'server_id': $('#new-le-server_id').val(),
|
||||
'domains': domains,
|
||||
'email': email,
|
||||
'type': type,
|
||||
'api_key': api_key,
|
||||
'api_token': api_token,
|
||||
'description': $('#new-le-description').val(),
|
||||
}
|
||||
$.ajax({
|
||||
url: '/service/letsencrypt',
|
||||
method: 'POST',
|
||||
data: JSON.stringify(jsonData),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
success: function (data) {
|
||||
if (data.status === 'failed') {
|
||||
toastr.error(data);
|
||||
} else {
|
||||
getLe(data['id'], dialogId);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
function removeLe(leId) {
|
||||
$("#lets-" + leId).css("background-color", "#f2dede");
|
||||
$.ajax({
|
||||
url: '/service/letsencrypt/' + leId,
|
||||
method: 'DELETE',
|
||||
contentType: "application/json; charset=utf-8",
|
||||
statusCode: {
|
||||
204: function (xhr) {
|
||||
$("#lets-" + leId).remove();
|
||||
},
|
||||
404: function (xhr) {
|
||||
$("#lets-" + leId).remove();
|
||||
}
|
||||
},
|
||||
success: function (data) {
|
||||
if (data) {
|
||||
if (data.status === "failed") {
|
||||
toastr.error(data);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
function confirmDeleteLe(id) {
|
||||
$( "#dialog-confirm" ).dialog({
|
||||
resizable: false,
|
||||
height: "auto",
|
||||
width: 400,
|
||||
modal: true,
|
||||
title: delete_word + " Let's encrypt?",
|
||||
buttons: [{
|
||||
text: delete_word,
|
||||
click: function () {
|
||||
$(this).dialog("close");
|
||||
removeLe(id);
|
||||
}
|
||||
},{
|
||||
text: cancel_word,
|
||||
click: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}]
|
||||
});
|
||||
}
|
||||
function openLeDialog() {
|
||||
$("#le-add-table").dialog({
|
||||
autoOpen: true,
|
||||
resizable: false,
|
||||
height: "auto",
|
||||
width: 500,
|
||||
modal: true,
|
||||
title: $('#translate').attr('data-create') + " Let's encrypt",
|
||||
show: {
|
||||
effect: "fade",
|
||||
duration: 200
|
||||
},
|
||||
hide: {
|
||||
effect: "fade",
|
||||
duration: 200
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
text: $('#translate').attr('data-create'),
|
||||
click: function () {
|
||||
addLe($(this));
|
||||
}
|
||||
}, {
|
||||
text: cancel_word,
|
||||
click: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
function getLe(leId, dialogId) {
|
||||
$.ajax({
|
||||
url: '/service/letsencrypt/' + leId + "?recurse=True",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
success: function (data) {
|
||||
if (data.status === 'failed') {
|
||||
toastr.error(data);
|
||||
} else {
|
||||
showLe(data);
|
||||
$.getScript(awesome);
|
||||
$(dialogId).dialog("close");
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
function getLes() {
|
||||
$.ajax({
|
||||
url: '/service/letsencrypts?recurse=True',
|
||||
contentType: "application/json; charset=utf-8",
|
||||
success: function (data) {
|
||||
if (data.status === 'failed') {
|
||||
toastr.error(data);
|
||||
} else {
|
||||
$('#le_table_body').empty();
|
||||
for (let k in data) {
|
||||
showLe(data[k]);
|
||||
}
|
||||
$.getScript(awesome);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
function showLe(data) {
|
||||
let list_domains = '';
|
||||
for (let d of eval(data['domains'])) {
|
||||
list_domains += d + ' ';
|
||||
if (d < data['domains'].length - 1) {
|
||||
list_domains += ', ';
|
||||
}
|
||||
}
|
||||
let le_tag = elem("tr", {"id":"lets-" + data['id']}, [
|
||||
elem("td", {"class":"padding10 first-collumn"}, data['server_id']['hostname']),
|
||||
elem("td", {"style": "width: 10%;"}, provides[data['type']]),
|
||||
elem("td", {"style": "width: 30%;"}, list_domains),
|
||||
elem("td", {"style": "width: 38%;"}, data['description']),
|
||||
elem("td", null, [
|
||||
elem("a", {"class":"delete","onclick":"confirmDeleteLe("+data['id']+")","title":"Delete","style":"cursor: pointer; width: 5%;"}),
|
||||
])
|
||||
])
|
||||
$('#le_table_body').append(le_tag);
|
||||
}
|
|
@ -353,7 +353,10 @@ function renderServiceChart(data, labels, server, service) {
|
|||
config.options.plugins.title.text = data[1] + ' ' + additional_title;
|
||||
let myChart = new Chart(ctx, config);
|
||||
myChart.update();
|
||||
stream_chart(myChart, service, server);
|
||||
charts.push(myChart)
|
||||
if (service !== 'waf') {
|
||||
stream_chart(myChart, service, server);
|
||||
}
|
||||
}
|
||||
function getNginxChartData(server) {
|
||||
$.ajax({
|
||||
|
|
|
@ -41,40 +41,13 @@ function show_current_page(id) {
|
|||
$( function() {
|
||||
$('.menu li ul li').each(function () {
|
||||
let link = $(this).find('a').attr('href');
|
||||
let link2 = link.split('#')[1];
|
||||
let link3 = link.split('/')[2];
|
||||
let link4 = link.split('/')[3];
|
||||
// if (cur_url[1] == null) {
|
||||
// cur_url[1] = 'haproxy';
|
||||
// }
|
||||
let full_uri = window.location.pathname
|
||||
let full_uri1 = window.location.hash
|
||||
let full_uri2 = cur_url[0] + '/' + cur_url[1] + '/' + cur_url[2]
|
||||
let full_uri3 = link2 + '/' + link3 + '/' + link4
|
||||
// console.log(link)
|
||||
// console.log(window.location.hash)
|
||||
let params = new URL(document.location.toString()).searchParams;
|
||||
// console.log(params.get("service"))
|
||||
// console.log(link)
|
||||
// console.log(full_uri)
|
||||
// console.log(full_uri1)
|
||||
// console.log(full_uri + "/" + full_uri1)
|
||||
// if (full_uri1 === '/service/haproxy') {
|
||||
// console.log(full_uri)
|
||||
// console.log(full_uri1)
|
||||
// }
|
||||
if (full_uri === link) {
|
||||
show_current_page($(this))
|
||||
// } else if (window.location.pathname.indexOf('add/haproxy#') != '-1' && link.indexOf('add/haproxy#proxy') != '-1') {
|
||||
// show_current_page($(this))
|
||||
} else if (link === full_uri + full_uri1) {
|
||||
show_current_page($(this))
|
||||
// } else if (link === '/admin#servers' && full_uri1 === '#servers') {
|
||||
// show_current_page($(this))
|
||||
// } else if (link === '/admin#ssh' && full_uri1 === '#ssh') {
|
||||
// show_current_page($(this))
|
||||
// } else if (link === '/add/haproxy#proxy' && full_uri1 === '#proxy') {
|
||||
// show_current_page($(this))
|
||||
} else if (link === '/add/haproxy#ssl' && full_uri1 === '#ssl' && params.get("service") != 'nginx') {
|
||||
show_current_page($(this))
|
||||
} else if (link === '/add/haproxy#ssl' && full_uri1 === '#ssl' && params.get("service") === 'nginx') {
|
||||
|
@ -1007,15 +980,20 @@ function changePassword() {
|
|||
effect: "fade",
|
||||
duration: 200
|
||||
},
|
||||
buttons: {
|
||||
"Change": function () {
|
||||
changeUserPasswordItOwn($(this));
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
$('#missmatchpass').hide();
|
||||
buttons: [
|
||||
{
|
||||
text: $('#translate').attr('data-change'),
|
||||
click: function () {
|
||||
changeUserPasswordItOwn($(this));
|
||||
}
|
||||
}, {
|
||||
text: cancel_word,
|
||||
click: function () {
|
||||
$(this).dialog("close");
|
||||
$('#missmatchpass').hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
function changeUserPasswordItOwn(d) {
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
<script src="/static/js/add.js"></script>
|
||||
<script src="/static/js/edit_config.js"></script>
|
||||
<script src="/static/js/le.js"></script>
|
||||
<div id="tabs">
|
||||
<ul>
|
||||
<li><a href="#create" title="{{lang.words.add|title()}} {{lang.words.proxy}}: {{lang.words.create|title()}} {{lang.words.proxy}} - Roxy-WI">{{lang.words.create|title()}} {{lang.words.proxy}}</a></li>
|
||||
|
@ -92,29 +93,19 @@
|
|||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table>
|
||||
<table id="le_table">
|
||||
<caption><h3>Let's Encrypt</h3></caption>
|
||||
<tr class="overviewHead">
|
||||
<td class="padding10 first-collumn">{{lang.words.server|title()}}</td>
|
||||
<td>{{lang.words.domain|title()}}</td>
|
||||
<td>{{lang.words.email|title()}}</td>
|
||||
<td>{{lang.words.type|title()}}</td>
|
||||
<td>{{lang.words.domains|title()}}</td>
|
||||
<td>{{lang.words.desc|title()}}</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="padding10 first-collumn">
|
||||
{{ select('serv_for_lets', values=g.user_params['servers'], is_servers='true') }}
|
||||
</td>
|
||||
<td>
|
||||
{{ input('lets_domain', placeholder="example.com") }}
|
||||
</td>
|
||||
<td>
|
||||
{{ input('lets_email') }}
|
||||
</td>
|
||||
<td>
|
||||
<button id="lets_button">{{lang.words.w_get|title()}} {{lang.words.w_a}} {{lang.words.cert}}</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tbody id="le_table_body"></tbody>
|
||||
</table>
|
||||
<br /><span class="add-button" title="{{lang.words.create|title()}}" onclick="openLeDialog()">+ {{lang.words.create|title()}}</span>
|
||||
<div id="ajax-ssl"></div>
|
||||
</div>
|
||||
<div id="option">
|
||||
|
@ -366,4 +357,80 @@ for (var i = 0; i <= serv_ports.length; i++) {
|
|||
'This can be done easily in HAProxy by adding the keyword backup on the server line. If multiple backup servers are configured, only the first active one is used.">backup</label><input type="checkbox" name="backup" value="1" id="' + uniqId + '">');
|
||||
}
|
||||
</script>
|
||||
<div id="le-add-table" style="display: none;">
|
||||
<table class="overview" id="group-add-table-overview" title="{{lang.words.add|title()}} {{lang.words.w_a}} {{lang.words.new3}} {{lang.words.group2}}">
|
||||
{% include 'include/tr_validate_tips.html' %}
|
||||
<tr>
|
||||
<td class="padding20 first-collumn">
|
||||
{{ lang.words.server|title() }}
|
||||
</td>
|
||||
<td>
|
||||
{{ select('new-le-server_id', values=g.user_params['servers'], is_servers='true', by_id=1) }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="padding20 first-collumn">
|
||||
{{ lang.words.type|title() }}
|
||||
</td>
|
||||
<td>
|
||||
<select id="new-le-type">
|
||||
<option value="standalone">Stand alone</option>
|
||||
<option value="route53">Route 53</option>
|
||||
<option value="cloudflare">CloudFlare</option>
|
||||
<option value="digitalocean">DigitalOcean</option>
|
||||
<option value="linode">Linode</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="padding20 first-collumn">
|
||||
{{ lang.words.domains|title() }}
|
||||
</td>
|
||||
<td>
|
||||
{{ input('new-le-domain') }}
|
||||
<div class="tooltip tooltipTop">{{ lang.add_page.desc.comma_separated }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="le-standalone">
|
||||
<td class="padding20 first-collumn">
|
||||
{{ lang.words.email|title() }}
|
||||
</td>
|
||||
<td>
|
||||
{{ input('new-le-email') }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="le-dns" style="display: none;">
|
||||
<td class="padding20 first-collumn">
|
||||
{{ lang.words.token|title() }}
|
||||
</td>
|
||||
<td>
|
||||
{{ input('new-le-token') }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="le-aws" style="display: none;">
|
||||
<td class="padding20 first-collumn">
|
||||
Access key ID
|
||||
</td>
|
||||
<td>
|
||||
{{ input('new-le-access_key_id') }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="le-aws" style="display: none;">
|
||||
<td class="padding20 first-collumn">
|
||||
Secret access key
|
||||
</td>
|
||||
<td>
|
||||
{{ input('new-le-secret_access_key') }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="padding20 first-collumn">
|
||||
{{ lang.words.desc|title() }}
|
||||
</td>
|
||||
<td>
|
||||
{{ input('new-le-description') }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -4,16 +4,16 @@
|
|||
<div id="cluster-{{cluster.id}}" class="div-server-hapwi">
|
||||
<div class="server-name">
|
||||
<a href="/ha/cluster/{{cluster.id}}" title="{{lang.words.open|title()}} {{lang.words.cluster|replace("'", "")}}">
|
||||
<span id="cluster-name-{{cluster.id}}">{{cluster.name}}</span>
|
||||
<span id="cluster-name-{{cluster.id}}">{{cluster.name|replace("'", "")}}</span>
|
||||
<span id="cluster-desc-{{cluster.id}}">{% if cluster.desc != '' %} ({{cluster.description|replace("'", "")}}) {% endif %}</span>
|
||||
</a>
|
||||
<span class="server-action">
|
||||
{% if g.user_params['role'] <= 3 %}
|
||||
<a class="plus" onclick="add_vip_ha_cluster('{{cluster.id}}', '{{cluster.name}}')"></a>
|
||||
<a class="plus" onclick="add_vip_ha_cluster('{{cluster.id}}', '{{cluster.name|replace("'", "")}}')"></a>
|
||||
<a class="edit" onclick="createHaClusterStep1(true, '{{cluster.id}}')"></a>
|
||||
<a class="delete" onclick="confirmDeleteCluster('{{cluster.id}}')"></a>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('main.service_history', service='cluster', server_ip=cluster.id) }}" title="{{lang.words.view|title()}} {{lang.words.history3}} {{cluster.name}}" class="history" style="margin: 0 5px 0 10px;"></a>
|
||||
<a href="{{ url_for('main.service_history', service='cluster', server_ip=cluster.id) }}" title="{{lang.words.view|title()}} {{lang.words.history3}} {{cluster.name|replace("'", "")}}" class="history" style="margin: 0 5px 0 10px;"></a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="server-desc">
|
||||
|
@ -47,7 +47,7 @@
|
|||
<span id="cluster-vip">
|
||||
{%- for vip in vips %}
|
||||
{% if g.user_params['role'] <= 2 %}
|
||||
<a style="cursor: pointer;" onclick="add_vip_ha_cluster('{{vip.cluster_id}}', '{{cluster.name}}', '{{vip.id}}', '{{vip.vip}}', 1)" title="{{lang.words.edit|title()}} VIP">{{vip.vip}}</a>
|
||||
<a style="cursor: pointer;" onclick="add_vip_ha_cluster('{{vip.cluster_id}}', '{{cluster.name|replace("'", "")}}', '{{vip.id}}', '{{vip.vip}}', 1)" title="{{lang.words.edit|title()}} VIP">{{vip.vip}}</a>
|
||||
{% else %}
|
||||
{{vip.vip}}
|
||||
{%- endif -%}
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
data-installing="{{lang.words.installing|title()}}" data-creating="{{lang.words.creating|title()}}" data-roxywi_timeout="{{lang.ha_page.roxywi_timeout}}"
|
||||
data-check_apache_log="{{lang.ha_page.check_apache_log}}" data-was_installed="{{lang.ha_page.was_installed}}" data-start_enter="{{lang.ha_page.start_enter}}"
|
||||
data-apply="{{lang.words.apply|title()}}" data-reconfigure="{{lang.words.reconfigure|title()}}" data-server="{{lang.words.server|title()}}" data-port="{{lang.words.port}}"
|
||||
data-weight="{{lang.words.weight}}" data-uptime="{{lang.words.uptime}}" data-downtime="{{lang.words.downtime}}" />
|
||||
data-weight="{{lang.words.weight}}" data-uptime="{{lang.words.uptime}}" data-downtime="{{lang.words.downtime}}" data-create="{{lang.words.create|title()}}" />
|
||||
{% include 'include/main_head.html' %}
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -404,6 +404,7 @@
|
|||
"option_temp": "Create, edit and delete options with given parameters. And after use them as autocomplete in the 'Add' sections",
|
||||
"server_temp": "Create, edit and delete servers. And after use them as autocomplete in the 'Add' sections",
|
||||
"use_add": "And use it in the 'Add' sections",
|
||||
"comma_separated": "You can specify several, separated by a comma or a space",
|
||||
},
|
||||
"buttons": {
|
||||
"disable_ssl_check": "Disable SSL check",
|
||||
|
@ -870,6 +871,7 @@
|
|||
"rule": "rule",
|
||||
"existing": "existing",
|
||||
"domain": "domain",
|
||||
"domains": "domains",
|
||||
"all": "all",
|
||||
"just": "just",
|
||||
"without": "without",
|
||||
|
|
|
@ -404,6 +404,7 @@
|
|||
"option_temp": "Créer, modifier et supprimer des options avec des paramètres donnés. Et ensuite les utiliser comme autocomplétion dans les sections 'Ajouter'",
|
||||
"server_temp": "Créer, modifier et supprimer des serveurs. Et par la suite, utilisez-les en tant que liste déroulante dans les sections 'Ajouter'",
|
||||
"use_add": "Et utilisez-le dans les sections 'Ajouter'.",
|
||||
"comma_separated": "Vous pouvez en spécifier plusieurs, séparés par une virgule ou un espace",
|
||||
},
|
||||
"buttons": {
|
||||
"disable_ssl_check": "Désactiver la vérification SSL",
|
||||
|
@ -870,6 +871,7 @@
|
|||
"rule": "règle",
|
||||
"existing": "existant",
|
||||
"domain": "domaine",
|
||||
"domains": "domaines",
|
||||
"all": "tout",
|
||||
"just": "juste",
|
||||
"without": "sans",
|
||||
|
|
|
@ -404,6 +404,7 @@
|
|||
"option_temp": "Crie, edite e exclua opções com determinados parâmetros. Usá-los como preenchimento automático nas seções 'Adicionar'",
|
||||
"server_temp": "Crie, edite e exclua servidores com determinados parâmetros. Usá-los como preenchimento automático nas seções 'Adicionar'",
|
||||
"use_add": "Usa isso as seções 'Adicionar'",
|
||||
"comma_separated": "Você pode especificar vários, separados por uma vírgula ou um espaço",
|
||||
},
|
||||
"buttons": {
|
||||
"disable_ssl_check": "Ativar a verificação de SSL",
|
||||
|
@ -868,8 +869,9 @@
|
|||
"display": "exibir",
|
||||
"default_backend": "Default backend",
|
||||
"rule": "regra",
|
||||
"existing": "existing",
|
||||
"domain": "domain",
|
||||
"existing": "existente",
|
||||
"domain": "domínio",
|
||||
"domains": "domínios",
|
||||
"all": "todos",
|
||||
"just": "somente",
|
||||
"without": "sem",
|
||||
|
|
|
@ -404,6 +404,7 @@
|
|||
"option_temp": "Создавать, редактировать и удалять опции с заданными параметрами. И после использовать их как автозаполнение в разделах «Добавить прокси»",
|
||||
"server_temp": "Создание, редактирование и удаление серверов. И после использовать их как автозаполнение в разделах «Добавить прокси»",
|
||||
"use_add": "И использовать их в разделах «Добавить прокси»",
|
||||
"comma_separated": "Можно указать несколько, разделенных запятой, либо пробелом",
|
||||
},
|
||||
"buttons": {
|
||||
"disable_ssl_check": "Отключить проверку SSL",
|
||||
|
@ -870,6 +871,7 @@
|
|||
"rule": "правило",
|
||||
"existing": "существующие",
|
||||
"domain": "домен",
|
||||
"domains": "домены",
|
||||
"all": "все",
|
||||
"just": "только",
|
||||
"without": "без",
|
||||
|
|
|
@ -5,6 +5,7 @@ from flask import render_template, request, jsonify, g
|
|||
from playhouse.shortcuts import model_to_dict
|
||||
|
||||
import app.modules.db.ha_cluster as ha_sql
|
||||
import app.modules.db.service as service_sql
|
||||
import app.modules.roxywi.common as roxywi_common
|
||||
import app.modules.common.common as common
|
||||
import app.modules.service.ha_cluster as ha_cluster
|
||||
|
@ -39,36 +40,87 @@ class HAView(MethodView):
|
|||
type: 'integer'
|
||||
responses:
|
||||
200:
|
||||
description: Successful operation
|
||||
description: HA details retrieved successfully
|
||||
schema:
|
||||
type: 'object'
|
||||
type: object
|
||||
properties:
|
||||
description:
|
||||
type: 'string'
|
||||
type: string
|
||||
description: Description of the HA
|
||||
eth:
|
||||
type: 'string'
|
||||
type: string
|
||||
description: Ethernet interface
|
||||
group_id:
|
||||
type: 'integer'
|
||||
haproxy:
|
||||
type: 'integer'
|
||||
type: integer
|
||||
description: Group ID
|
||||
id:
|
||||
type: 'integer'
|
||||
type: integer
|
||||
description: ID of the HA
|
||||
name:
|
||||
type: 'string'
|
||||
nginx:
|
||||
type: 'integer'
|
||||
type: string
|
||||
description: Name of the listener
|
||||
pos:
|
||||
type: 'integer'
|
||||
type: integer
|
||||
description: Position
|
||||
return_master:
|
||||
type: integer
|
||||
description: Return master flag
|
||||
servers:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
eth:
|
||||
type: string
|
||||
description: Ethernet interface
|
||||
id:
|
||||
type: integer
|
||||
description: Server ID
|
||||
master:
|
||||
type: integer
|
||||
description: Master flag
|
||||
services:
|
||||
type: object
|
||||
properties:
|
||||
apache:
|
||||
type: object
|
||||
properties:
|
||||
docker:
|
||||
type: integer
|
||||
description: Docker flag for Apache
|
||||
enabled:
|
||||
type: integer
|
||||
description: Enabled flag for Apache
|
||||
haproxy:
|
||||
type: object
|
||||
properties:
|
||||
docker:
|
||||
type: integer
|
||||
description: Docker flag for HAProxy
|
||||
enabled:
|
||||
type: integer
|
||||
description: Enabled flag for HAProxy
|
||||
nginx:
|
||||
type: object
|
||||
properties:
|
||||
docker:
|
||||
type: integer
|
||||
description: Docker flag for NGINX
|
||||
enabled:
|
||||
type: integer
|
||||
description: Enabled flag for NGINX
|
||||
syn_flood:
|
||||
type: 'integer'
|
||||
type: integer
|
||||
description: SYN Flood protection flag
|
||||
use_src:
|
||||
type: 'integer'
|
||||
type: integer
|
||||
description: Use source flag
|
||||
vip:
|
||||
type: 'string'
|
||||
type: string
|
||||
description: Virtual IP address
|
||||
virt_server:
|
||||
type: 'boolean'
|
||||
default:
|
||||
description: Unexpected error
|
||||
type: integer
|
||||
description: Virtual server flag
|
||||
"""
|
||||
if not cluster_id:
|
||||
if request.method == 'GET':
|
||||
|
@ -88,25 +140,48 @@ class HAView(MethodView):
|
|||
cluster_services = ha_sql.select_cluster_services(cluster_id)
|
||||
vip = ha_sql.select_cluster_vip(cluster_id, router_id)
|
||||
is_virt = ha_sql.check_ha_virt(vip.id)
|
||||
if vip.use_src:
|
||||
use_src = 1
|
||||
else:
|
||||
use_src = 0
|
||||
|
||||
for cluster in clusters:
|
||||
settings = model_to_dict(cluster)
|
||||
settings.setdefault('vip', vip.vip)
|
||||
settings.setdefault('virt_server', is_virt)
|
||||
settings.setdefault('use_src', vip.use_src)
|
||||
settings.setdefault('use_src', use_src)
|
||||
settings.setdefault('return_master', vip.return_master)
|
||||
settings['servers'] = []
|
||||
settings['services'] = {
|
||||
'haproxy': {
|
||||
'enabled': 0,
|
||||
'docker': 0
|
||||
},
|
||||
'nginx': {
|
||||
'enabled': 0,
|
||||
'docker': 0
|
||||
},
|
||||
'apache': {
|
||||
'enabled': 0,
|
||||
'docker': 0
|
||||
}
|
||||
}
|
||||
|
||||
for slave in slaves:
|
||||
server_id = slave[0]
|
||||
if slave[31]:
|
||||
settings.setdefault('eth', slave[32])
|
||||
server_settings = {'id': server_id, 'eth': slave[32], 'master': slave[31]}
|
||||
settings['servers'].append(server_settings)
|
||||
|
||||
for c_s in cluster_services:
|
||||
is_dockerized = int(service_sql.select_service_setting(server_id, c_s.service_id, 'dockerized'))
|
||||
if int(c_s.service_id) == 1:
|
||||
settings.setdefault('haproxy', 1)
|
||||
elif int(c_s.service_id) == 2:
|
||||
settings.setdefault('nginx', 1)
|
||||
elif int(c_s.service_id) == 4:
|
||||
settings.setdefault('apache', 1)
|
||||
settings['services']['haproxy'] = {'enabled': 1, 'docker': is_dockerized}
|
||||
if int(c_s.service_id) == 2:
|
||||
settings['services']['nginx'] = {'enabled': 1, 'docker': is_dockerized}
|
||||
if int(c_s.service_id) == 4:
|
||||
settings['services']['apache'] = {'enabled': 1, 'docker': is_dockerized}
|
||||
|
||||
return jsonify(settings)
|
||||
|
||||
|
@ -353,42 +428,74 @@ class HAVIPView(MethodView):
|
|||
description: 'Can be only "cluster"'
|
||||
required: true
|
||||
type: 'string'
|
||||
- in: 'path'
|
||||
name: 'cluster_id'
|
||||
description: 'ID of the HA cluster to retrieve'
|
||||
- name: cluster_id
|
||||
in: path
|
||||
type: integer
|
||||
required: true
|
||||
type: 'integer'
|
||||
- in: 'path'
|
||||
name: 'vip_id'
|
||||
description: 'ID of the VIP to retrieve'
|
||||
description: ID of the cluster
|
||||
- name: vip_id
|
||||
in: path
|
||||
type: integer
|
||||
required: true
|
||||
type: 'integer'
|
||||
description: ID of the VIP
|
||||
responses:
|
||||
200:
|
||||
description: Successful operation
|
||||
description: HAVIP details retrieved successfully
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
cluster_id:
|
||||
type: 'integer'
|
||||
type: integer
|
||||
description: ID of the cluster
|
||||
eth:
|
||||
type: string
|
||||
description: Ethernet interface
|
||||
id:
|
||||
type: 'integer'
|
||||
type: integer
|
||||
description: ID of the HAVIP
|
||||
return_master:
|
||||
type: 'integer'
|
||||
type: integer
|
||||
description: Return master flag
|
||||
router_id:
|
||||
type: 'integer'
|
||||
type: integer
|
||||
description: ID of the router
|
||||
servers:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
eth:
|
||||
type: string
|
||||
description: Ethernet interface
|
||||
id:
|
||||
type: integer
|
||||
description: Server ID
|
||||
master:
|
||||
type: integer
|
||||
description: Master flag
|
||||
use_src:
|
||||
type: 'integer'
|
||||
type: integer
|
||||
description: Use source flag
|
||||
vip:
|
||||
type: 'string'
|
||||
default:
|
||||
description: Unexpected error
|
||||
type: string
|
||||
description: Virtual IP address
|
||||
virt_server:
|
||||
type: integer
|
||||
description: Virtual server flag
|
||||
"""
|
||||
try:
|
||||
vip = ha_sql.select_cluster_vip_by_vip_id(cluster_id, vip_id)
|
||||
slaves = ha_sql.select_cluster_slaves(cluster_id, vip.router_id)
|
||||
settings = model_to_dict(vip, recurse=False)
|
||||
is_virt = ha_sql.check_ha_virt(vip.id)
|
||||
settings.setdefault('virt_server', is_virt)
|
||||
settings['servers'] = []
|
||||
for slave in slaves:
|
||||
server_id = slave[0]
|
||||
if slave[31]:
|
||||
settings.setdefault('eth', slave[32])
|
||||
server_settings = {'id': server_id, 'eth': slave[32], 'master': slave[31]}
|
||||
settings['servers'].append(server_settings)
|
||||
return jsonify(settings)
|
||||
except Exception as e:
|
||||
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot get VIP')
|
||||
|
|
|
@ -0,0 +1,392 @@
|
|||
import ast
|
||||
from typing import Union
|
||||
|
||||
from flask.views import MethodView
|
||||
from flask_pydantic import validate
|
||||
from flask import jsonify
|
||||
from flask_jwt_extended import jwt_required
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
|
||||
import app.modules.db.sql as sql
|
||||
import app.modules.db.le as le_sql
|
||||
import app.modules.db.server as server_sql
|
||||
import app.modules.common.common as common
|
||||
import app.modules.roxywi.common as roxywi_common
|
||||
import app.modules.service.installation as service_mod
|
||||
from app.modules.db.db_model import LetsEncrypt
|
||||
from app.modules.server.ssh import return_ssh_keys_path
|
||||
from app.middleware import get_user_params, page_for_admin, check_group
|
||||
from app.modules.roxywi.class_models import LetsEncryptRequest, LetsEncryptDeleteRequest, IdResponse, GroupQuery, BaseResponse
|
||||
from app.modules.common.common_classes import SupportClass
|
||||
|
||||
|
||||
class LetsEncryptView(MethodView):
|
||||
methods = ['GET', 'POST', 'PUT', 'DELETE']
|
||||
decorators = [jwt_required(), get_user_params(), page_for_admin(level=3), check_group()]
|
||||
|
||||
@validate(query=GroupQuery)
|
||||
def get(self, le_id: int, query: GroupQuery):
|
||||
"""
|
||||
Get Let's Encrypt details.
|
||||
---
|
||||
tags:
|
||||
- Let's Encrypt
|
||||
parameters:
|
||||
- name: le_id
|
||||
in: path
|
||||
type: integer
|
||||
required: true
|
||||
description: ID of the Let's Encrypt configuration
|
||||
- name: group_id
|
||||
in: query
|
||||
type: integer
|
||||
required: false
|
||||
description: ID of the group (only for role superAdmin)
|
||||
responses:
|
||||
200:
|
||||
description: Let's Encrypt details retrieved successfully
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
api_key:
|
||||
type: string
|
||||
description: API key
|
||||
api_token:
|
||||
type: string
|
||||
description: API token
|
||||
description:
|
||||
type: string
|
||||
description: Description of the Let's Encrypt configuration
|
||||
domains:
|
||||
type: array
|
||||
description: List of domains associated with the Let's Encrypt configuration
|
||||
email:
|
||||
type: string
|
||||
description: Email associated with the Let's Encrypt account
|
||||
id:
|
||||
type: integer
|
||||
description: ID of the Let's Encrypt configuration
|
||||
server_id:
|
||||
type: integer
|
||||
description: ID of the server
|
||||
type:
|
||||
type: string
|
||||
description: Type of the Let's Encrypt configuration
|
||||
enum: ['standalone', 'route53', 'cloudflare', 'digitalocean', 'linode']
|
||||
"""
|
||||
group_id = SupportClass.return_group_id(query)
|
||||
try:
|
||||
le = le_sql.get_le_with_group(le_id, group_id)
|
||||
le_dict = model_to_dict(le, recurse=False)
|
||||
le_dict['domains'] = ast.literal_eval(le_dict['domains'])
|
||||
return jsonify(le_dict)
|
||||
except Exception as e:
|
||||
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot get Let\'s Encrypt')
|
||||
|
||||
@validate(body=LetsEncryptRequest)
|
||||
def post(self, body: LetsEncryptRequest):
|
||||
"""
|
||||
Create a Let's Encrypt configuration.
|
||||
---
|
||||
tags:
|
||||
- Let's Encrypt
|
||||
parameters:
|
||||
- name: group_id
|
||||
in: query
|
||||
type: integer
|
||||
required: false
|
||||
description: ID of the group (only for role superAdmin)
|
||||
- name: body
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
api_key:
|
||||
type: string
|
||||
description: API key
|
||||
api_token:
|
||||
type: string
|
||||
description: API token
|
||||
description:
|
||||
type: string
|
||||
description: Description of the Let's Encrypt configuration
|
||||
domains:
|
||||
type: array
|
||||
description: List of domains associated with the Let's Encrypt configuration
|
||||
email:
|
||||
type: string
|
||||
description: Email associated with the Let's Encrypt account
|
||||
id:
|
||||
type: integer
|
||||
description: ID of the Let's Encrypt configuration
|
||||
server_id:
|
||||
type: integer
|
||||
description: ID of the server
|
||||
type:
|
||||
type: string
|
||||
description: Type of the Let's Encrypt configuration
|
||||
enum: ['standalone', 'route53', 'cloudflare', 'digitalocean', 'linode']
|
||||
responses:
|
||||
201:
|
||||
description: Let's Encrypt configuration created successfully
|
||||
"""
|
||||
try:
|
||||
self._create_env(body)
|
||||
last_id = le_sql.insert_le(**body.model_dump(mode='json'))
|
||||
return IdResponse(id=last_id).model_dump(), 201
|
||||
except Exception as e:
|
||||
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot create Let\'s Encrypt')
|
||||
|
||||
@validate(body=LetsEncryptRequest, query=GroupQuery)
|
||||
def put(self, le_id: int, body: LetsEncryptRequest, query: GroupQuery):
|
||||
"""
|
||||
Update a Let's Encrypt configuration.
|
||||
---
|
||||
tags:
|
||||
- Let's Encrypt
|
||||
parameters:
|
||||
- name: le_id
|
||||
in: path
|
||||
type: integer
|
||||
required: true
|
||||
description: ID of the Let's Encrypt configuration
|
||||
- name: group_id
|
||||
in: query
|
||||
type: integer
|
||||
required: false
|
||||
description: ID of the group (only for role superAdmin)
|
||||
- name: body
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
api_key:
|
||||
type: string
|
||||
description: API key
|
||||
api_token:
|
||||
type: string
|
||||
description: API token
|
||||
description:
|
||||
type: string
|
||||
description: Description of the Let's Encrypt configuration
|
||||
domains:
|
||||
type: array
|
||||
description: List of domains associated with the Let's Encrypt configuration
|
||||
email:
|
||||
type: string
|
||||
description: Email associated with the Let's Encrypt account
|
||||
id:
|
||||
type: integer
|
||||
description: ID of the Let's Encrypt configuration
|
||||
server_id:
|
||||
type: integer
|
||||
description: ID of the server
|
||||
type:
|
||||
type: string
|
||||
description: Type of the Let's Encrypt configuration
|
||||
enum: ['standalone', 'route53', 'cloudflare', 'digitalocean', 'linode']
|
||||
responses:
|
||||
201:
|
||||
description: Let's Encrypt configuration updated successfully
|
||||
"""
|
||||
group_id = SupportClass.return_group_id(query)
|
||||
try:
|
||||
le = le_sql.get_le_with_group(le_id, group_id)
|
||||
except Exception as e:
|
||||
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot find Let\'s Encrypt')
|
||||
|
||||
try:
|
||||
le_dict = _return_domains_list(le)
|
||||
data = LetsEncryptRequest(**le_dict)
|
||||
self._create_env(data, 'delete')
|
||||
except Exception as e:
|
||||
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot update Let\'s Encrypt on server')
|
||||
|
||||
try:
|
||||
le_sql.update_le(le_id, **body.model_dump(mode='json'))
|
||||
self._create_env(body, 'install')
|
||||
return IdResponse(id=le_id).model_dump(), 201
|
||||
except Exception as e:
|
||||
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot update Let\'s Encrypt')
|
||||
|
||||
@validate(query=GroupQuery)
|
||||
def delete(self, le_id: int, query: GroupQuery):
|
||||
"""
|
||||
Delete Let's Encrypt details.
|
||||
---
|
||||
tags:
|
||||
- Let's Encrypt
|
||||
parameters:
|
||||
- name: le_id
|
||||
in: path
|
||||
type: integer
|
||||
required: true
|
||||
description: ID of the Let's Encrypt configuration
|
||||
- name: group_id
|
||||
in: query
|
||||
type: integer
|
||||
required: false
|
||||
description: ID of the group (only for role superAdmin)
|
||||
responses:
|
||||
204:
|
||||
description: Let's Encrypt deleted successfully
|
||||
"""
|
||||
group_id = SupportClass.return_group_id(query)
|
||||
try:
|
||||
le = le_sql.get_le_with_group(le_id, group_id)
|
||||
except Exception as e:
|
||||
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot find Let\'s Encrypt')
|
||||
|
||||
try:
|
||||
le_dict = _return_domains_list(le)
|
||||
le_dict['emails'] = None
|
||||
data = LetsEncryptDeleteRequest(**le_dict)
|
||||
self._create_env(data, action='delete')
|
||||
except Exception as e:
|
||||
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot delete Let\'s Encrypt from server')
|
||||
|
||||
try:
|
||||
le_sql.delete_le(le_id)
|
||||
return BaseResponse().model_dump(mode='json'), 204
|
||||
except Exception as e:
|
||||
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot delete Let\'s Encrypt')
|
||||
|
||||
def _create_env(self, data: Union[LetsEncryptRequest, LetsEncryptDeleteRequest], action: str = 'install' ):
|
||||
server_ips = []
|
||||
server_ip = 'localhost'
|
||||
domains_command = ''
|
||||
servers = {}
|
||||
main_domain = data.domains[0]
|
||||
inv = {"server": {"hosts": {}}}
|
||||
masters = server_sql.is_master(server_ip)
|
||||
ssl_path = common.return_nice_path(sql.get_setting('cert_path'), is_service=0)
|
||||
|
||||
if data.type == 'standalone':
|
||||
server_ip = server_sql.get_server_by_id(data.server_id).ip
|
||||
ssh_settings = return_ssh_keys_path(server_ip)
|
||||
servers[server_ip] = f"{ssh_settings['user']}@{ssh_settings['key']}"
|
||||
ansible_role = 'letsencrypt_standalone'
|
||||
else:
|
||||
master_ip = server_sql.get_server_by_id(data.server_id).ip
|
||||
ssh_settings = return_ssh_keys_path(master_ip)
|
||||
servers[master_ip] = f"{ssh_settings['user']}@{ssh_settings['key']}"
|
||||
ansible_role = 'letsencrypt'
|
||||
|
||||
for domain in data.domains:
|
||||
domains_command += f' -d {domain}'
|
||||
|
||||
for master in masters:
|
||||
if master[0] is not None:
|
||||
ssh_settings = return_ssh_keys_path(master[0])
|
||||
servers[master[0]] = f"{ssh_settings['user']}@{ssh_settings['key']}"
|
||||
|
||||
inv['server']['hosts'][master[0]] = {
|
||||
'token': data.api_token,
|
||||
'secret_key': data.api_key,
|
||||
'email': data.email,
|
||||
'ssl_path': ssl_path,
|
||||
'domains_command': domains_command,
|
||||
'main_domain': main_domain,
|
||||
'servers': servers,
|
||||
'action': action,
|
||||
'cert_type': data.type
|
||||
}
|
||||
server_ips.append(master[0])
|
||||
|
||||
inv['server']['hosts'][server_ip] = {
|
||||
'token': data.api_token,
|
||||
'secret_key': data.api_key,
|
||||
'email': data.email,
|
||||
'ssl_path': ssl_path,
|
||||
'domains_command': domains_command,
|
||||
'main_domain': main_domain,
|
||||
'servers': servers,
|
||||
'action': action,
|
||||
'cert_type': data.type
|
||||
}
|
||||
|
||||
server_ips.append(server_ip)
|
||||
if data.type != 'standalone':
|
||||
try:
|
||||
output = service_mod.run_ansible_locally(inv, ansible_role)
|
||||
except Exception as e:
|
||||
raise e
|
||||
else:
|
||||
try:
|
||||
output = service_mod.run_ansible(inv, server_ips, 'letsencrypt')
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
if len(output['failures']) > 0 or len(output['dark']) > 0:
|
||||
raise Exception('Cannot create certificate. Check Apache error log')
|
||||
|
||||
|
||||
class LetsEncryptsView(MethodView):
|
||||
methods = ['GET']
|
||||
decorators = [jwt_required(), get_user_params(), page_for_admin(level=3), check_group()]
|
||||
|
||||
@validate(query=GroupQuery)
|
||||
def get(self, query: GroupQuery):
|
||||
"""
|
||||
Get all Let's Encrypt configurations.
|
||||
---
|
||||
tags:
|
||||
- Let's Encrypt
|
||||
parameters:
|
||||
- name: group_id
|
||||
in: query
|
||||
type: integer
|
||||
required: false
|
||||
description: ID of the group (only for role superAdmin)
|
||||
responses:
|
||||
200:
|
||||
description: List of Let's Encrypt configurations retrieved successfully
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
api_key:
|
||||
type: string
|
||||
description: API key
|
||||
api_token:
|
||||
type: string
|
||||
description: API token
|
||||
description:
|
||||
type: string
|
||||
description: Description of the Let's Encrypt configuration
|
||||
domains:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: Domains associated with the Let's Encrypt configuration
|
||||
email:
|
||||
type: string
|
||||
description: Email associated with the Let's Encrypt account
|
||||
id:
|
||||
type: integer
|
||||
description: ID of the Let's Encrypt configuration
|
||||
server_id:
|
||||
type: integer
|
||||
description: ID of the server
|
||||
type:
|
||||
type: string
|
||||
description: Type of the Let's Encrypt configuration
|
||||
"""
|
||||
group_id = SupportClass.return_group_id(query)
|
||||
le_list = []
|
||||
try:
|
||||
les = le_sql.select_le_with_group(group_id)
|
||||
for le in les:
|
||||
le_list.append(_return_domains_list(le, query.recurse))
|
||||
return jsonify(le_list)
|
||||
except Exception as e:
|
||||
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot get Let\'s Encrypts')
|
||||
|
||||
|
||||
def _return_domains_list(le: LetsEncrypt, recurse: bool = False) -> dict:
|
||||
le_dict = model_to_dict(le, recurse=recurse)
|
||||
le_dict['domains'] = ast.literal_eval(le_dict['domains'])
|
||||
return le_dict
|
|
@ -21,6 +21,7 @@ Flask-APScheduler==1.13.1
|
|||
Flask-Caching==2.3.0
|
||||
Flask-JWT-Extended==4.6.0
|
||||
Flask-Pydantic==0.12.0
|
||||
pydantic[email]
|
||||
flask-swagger==0.2.14
|
||||
ansible-core==2.16.3
|
||||
ansible-runner==2.3.1
|
||||
|
|
Loading…
Reference in New Issue