haproxy manager
This commit is contained in:
parent
9c52edd53a
commit
305fffba42
10
Dockerfile
10
Dockerfile
@ -1,9 +1,15 @@
|
||||
FROM python:3.12-slim
|
||||
RUN apt update -y && apt dist-upgrade -y && apt install socat haproxy -y && apt clean && rm -rf /var/lib/apt/lists/*
|
||||
RUN apt update -y && apt dist-upgrade -y && apt install socat haproxy cron certbot -y && apt clean && rm -rf /var/lib/apt/lists/*
|
||||
WORKDIR /haproxy
|
||||
COPY ./templates /haproxy/templates
|
||||
COPY requirements.txt /haproxy/
|
||||
COPY haproxy_manager.py /haproxy/
|
||||
COPY scripts /haproxy/scripts
|
||||
RUN chmod +x /haproxy/scripts/*
|
||||
RUN pip install -r requirements.txt
|
||||
RUN echo "0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(43200))' && certbot -q renew --no-random-sleep-on-renew" > /var/spool/cron/crontabs/root
|
||||
EXPOSE 80 443 8000
|
||||
#CMD ["python", "app.py"]
|
||||
# Add health check
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost:8000/health || exit 1
|
||||
CMD ["/haproxy/scripts/start-up.sh"]
|
103
README.md
103
README.md
@ -1,2 +1,105 @@
|
||||
# HAProxy Manager Base
|
||||
|
||||
A Flask-based API service for managing HAProxy configurations, domains, and SSL certificates.
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Health Check
|
||||
Check the status of the HAProxy Manager service.
|
||||
|
||||
```bash
|
||||
GET /health
|
||||
|
||||
# Response
|
||||
{
|
||||
"status": "healthy",
|
||||
"haproxy_status": "running",
|
||||
"database": "connected"
|
||||
}
|
||||
```
|
||||
|
||||
### Add Domain
|
||||
Add a new domain with backend servers configuration.
|
||||
|
||||
```bash
|
||||
POST /api/domain
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"domain": "example.com",
|
||||
"backend_name": "example_backend",
|
||||
"servers": [
|
||||
{
|
||||
"name": "server1",
|
||||
"address": "10.0.0.1",
|
||||
"port": 8080,
|
||||
"options": "check"
|
||||
},
|
||||
{
|
||||
"name": "server2",
|
||||
"address": "10.0.0.2",
|
||||
"port": 8080,
|
||||
"options": "check backup"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Response
|
||||
{
|
||||
"status": "success",
|
||||
"domain_id": 1
|
||||
}
|
||||
```
|
||||
|
||||
### Enable SSL
|
||||
Request and configure SSL certificate for a domain using Let's Encrypt.
|
||||
|
||||
```bash
|
||||
POST /api/ssl
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"domain": "example.com"
|
||||
}
|
||||
|
||||
# Response
|
||||
{
|
||||
"status": "success"
|
||||
}
|
||||
```
|
||||
|
||||
### Remove Domain
|
||||
Remove a domain and its associated backend configuration.
|
||||
|
||||
```bash
|
||||
DELETE /api/domain
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"domain": "example.com"
|
||||
}
|
||||
|
||||
# Response
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Domain configuration removed"
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Automatic HAProxy configuration generation
|
||||
- Let's Encrypt SSL certificate integration
|
||||
- Backend server management
|
||||
- Self-signed certificate generation for development
|
||||
- Health monitoring
|
||||
- Database-backed configuration storage
|
||||
|
||||
## Requirements
|
||||
|
||||
- HAProxy
|
||||
- Python 3.x
|
||||
- Flask
|
||||
- SQLite3
|
||||
- Certbot (for Let's Encrypt certificates)
|
||||
- OpenSSL (for self-signed certificates)
|
||||
|
@ -5,7 +5,6 @@ from pathlib import Path
|
||||
import subprocess
|
||||
import jinja2
|
||||
import socket
|
||||
import shutil
|
||||
import psutil
|
||||
|
||||
app = Flask(__name__)
|
||||
@ -95,6 +94,29 @@ def is_process_running(process_name):
|
||||
template_loader = jinja2.FileSystemLoader(TEMPLATE_DIR)
|
||||
template_env = jinja2.Environment(loader=template_loader)
|
||||
|
||||
@app.route('/health', methods=['GET'])
|
||||
def health_check():
|
||||
try:
|
||||
# Check if HAProxy is running
|
||||
haproxy_running = is_process_running('haproxy')
|
||||
|
||||
# Check if database is accessible
|
||||
with sqlite3.connect(DB_FILE) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('SELECT 1')
|
||||
cursor.fetchone()
|
||||
|
||||
return jsonify({
|
||||
'status': 'healthy',
|
||||
'haproxy_status': 'running' if haproxy_running else 'stopped',
|
||||
'database': 'connected'
|
||||
}), 200
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'status': 'unhealthy',
|
||||
'error': str(e)
|
||||
}), 500
|
||||
|
||||
@app.route('/api/domain', methods=['POST'])
|
||||
def add_domain():
|
||||
data = request.get_json()
|
||||
@ -164,6 +186,58 @@ def request_ssl():
|
||||
return jsonify({'status': 'success'})
|
||||
return jsonify({'status': 'error', 'message': 'Failed to obtain SSL certificate'})
|
||||
|
||||
@app.route('/api/domain', methods=['DELETE'])
|
||||
def remove_domain():
|
||||
data = request.get_json()
|
||||
domain = data.get('domain')
|
||||
|
||||
if not domain:
|
||||
return jsonify({'status': 'error', 'message': 'Domain is required'}), 400
|
||||
|
||||
try:
|
||||
with sqlite3.connect(DB_FILE) as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Get domain ID and check if it exists
|
||||
cursor.execute('SELECT id FROM domains WHERE domain = ?', (domain,))
|
||||
domain_result = cursor.fetchone()
|
||||
|
||||
if not domain_result:
|
||||
return jsonify({'status': 'error', 'message': 'Domain not found'}), 404
|
||||
|
||||
domain_id = domain_result[0]
|
||||
|
||||
# Get backend IDs associated with this domain
|
||||
cursor.execute('SELECT id FROM backends WHERE domain_id = ?', (domain_id,))
|
||||
backend_ids = [row[0] for row in cursor.fetchall()]
|
||||
|
||||
# Delete backend servers
|
||||
for backend_id in backend_ids:
|
||||
cursor.execute('DELETE FROM backend_servers WHERE backend_id = ?', (backend_id,))
|
||||
|
||||
# Delete backends
|
||||
cursor.execute('DELETE FROM backends WHERE domain_id = ?', (domain_id,))
|
||||
|
||||
# Delete domain
|
||||
cursor.execute('DELETE FROM domains WHERE id = ?', (domain_id,))
|
||||
|
||||
# Delete SSL certificate if it exists
|
||||
cursor.execute('SELECT ssl_cert_path FROM domains WHERE id = ? AND ssl_enabled = 1', (domain_id,))
|
||||
cert_result = cursor.fetchone()
|
||||
if cert_result and cert_result[0]:
|
||||
try:
|
||||
os.remove(cert_result[0])
|
||||
except OSError:
|
||||
pass # Ignore errors if file doesn't exist
|
||||
|
||||
# Regenerate HAProxy config
|
||||
generate_config()
|
||||
|
||||
return jsonify({'status': 'success', 'message': 'Domain configuration removed'})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
def generate_config():
|
||||
try:
|
||||
conn = sqlite3.connect(DB_FILE)
|
||||
@ -243,17 +317,19 @@ def generate_config():
|
||||
continue
|
||||
|
||||
# Write complete configuration to tmp
|
||||
temp_config_path = "/etc/haproxy/haproxy.cfg"
|
||||
|
||||
config_content = '\n'.join(config_parts)
|
||||
print("Final config content:", config_content) # Debug log
|
||||
|
||||
# Write complete configuration to tmp
|
||||
# Check HAProxy Configuration, and reload if it works
|
||||
with open("/tmp/haproxy_temp.cfg", 'w') as f:
|
||||
f.write('\n'.join(config_parts))
|
||||
result = subprocess.run(['haproxy', '-c', '-f', "/tmp/haproxy_temp.cfg"], capture_output=True)
|
||||
with open(temp_config_path, 'w') as f:
|
||||
f.write(config_content)
|
||||
|
||||
result = subprocess.run(['haproxy', '-c', '-f', temp_config_path], capture_output=True)
|
||||
if result.returncode == 0:
|
||||
shutil.copyfile("/tmp/haproxy_temp.cfg", HAPROXY_CONFIG_PATH)
|
||||
os.remove("/tmp/haproxy_temp.cfg")
|
||||
print("HAProxy configuration check passed")
|
||||
if is_process_running('haproxy'):
|
||||
subprocess.run(['echo', '"reload"', '|', 'socat', 'stdio', '/tmp/haproxy-cli'])
|
||||
else:
|
||||
|
@ -2,4 +2,5 @@
|
||||
|
||||
# Exit on error
|
||||
set -eo pipefail
|
||||
|
||||
cron &
|
||||
python /haproxy/haproxy_manager.py
|
||||
|
@ -1,9 +1,8 @@
|
||||
|
||||
backend {{ name }}-backend
|
||||
|
||||
option forwardfor
|
||||
http-request add-header X-CLIENT-IP %[src]
|
||||
{% if ssl_enabled %} ttp-request set-header X-Forwarded-Proto https if \{ ssl_fc \} {% endif %}
|
||||
{% if ssl_enabled %}http-request set-header X-Forwarded-Proto https if { ssl_fc }{% endif %}
|
||||
{% for server in servers %}
|
||||
server {{ server.name }} {{ server.address }}:{{ server.port }} {{ server.options }}
|
||||
server {{ server.server_name }} {{ server.server_address }}:{{ server.server_port }} {{ server.server_options }}
|
||||
{% endfor %}
|
@ -1,3 +1,4 @@
|
||||
|
||||
#Path Method {{ path }}
|
||||
acl {{ path }}-acl path_beg {{ path }}
|
||||
use_backend {{ name }}-backend if {{ path }}-acl
|
||||
|
Loading…
x
Reference in New Issue
Block a user