Page Menu
Home
DevCentral
Search
Configure Global Search
Log In
Files
F3752425
D3494.id9003.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
13 KB
Referenced Files
None
Subscribers
None
D3494.id9003.diff
View Options
diff --git a/PORTS b/PORTS
--- a/PORTS
+++ b/PORTS
@@ -1,3 +1,6 @@
+devserver
+ 2337 API api-exec HTTP
+
reserved-for-legacy-docker-migration-medium-priority
3000 Mastodon public HTTP
4000 Mastodon streaming HTTP
diff --git a/roles/devserver/api-exec/config.sls b/roles/devserver/api-exec/config.sls
new file mode 100644
--- /dev/null
+++ b/roles/devserver/api-exec/config.sls
@@ -0,0 +1,28 @@
+# -------------------------------------------------------------
+# API :: api-exec
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# License: Trivial work, not eligible to copyright
+# -------------------------------------------------------------
+
+{% set network = salt['node.resolve_network']() %}
+{% set half_num_cpus = grains["num_cpus"] / 2 %}
+
+/usr/local/etc/api-exec.conf:
+ file.managed:
+ - source: salt://roles/devserver/api-exec/files/api-exec.conf
+ - template: jinja
+ - context:
+ internal_ip: {{ network["private_ipv4_address"] }}
+ port: 2337
+
+ processes: {{ half_num_cpus | int }}
+
+ paths:
+ app: /srv/api-exec/src
+ venv: /srv/api-exec/venv
+
+/var/log/api-exec.log:
+ file.managed:
+ - user: nobody
+ - replace: False
diff --git a/roles/devserver/api-exec/files/api-exec.conf b/roles/devserver/api-exec/files/api-exec.conf
new file mode 100644
--- /dev/null
+++ b/roles/devserver/api-exec/files/api-exec.conf
@@ -0,0 +1,69 @@
+# -------------------------------------------------------------
+# API :: api-exec
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# License: Trivial work, not eligible to copyright
+# Source file: roles/devserver/api-exec/files/api-exec.conf
+# -------------------------------------------------------------
+#
+# <auto-generated>
+# This file is managed by our rOPS SaltStack repository.
+#
+# Changes to this file may cause incorrect behavior
+# and will be lost if the state is redeployed.
+# </auto-generated>
+
+# -------------------------------------------------------------
+# uWSGI
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+uwsgi:
+ module: server
+ callable: app
+
+ http-socket: {{ internal_ip }}:{{ port }}
+
+ master: true
+
+ processes: {{ processes }}
+ threads: 2
+
+ chdir: {{ paths.app }}
+ virtualenv: {{ paths.venv }}
+
+ safe-pidfile: /var/run/api-exec.pid
+
+# -------------------------------------------------------------
+# Global environment
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+env:
+ CCACHE_DIR: /var/cache/ccache
+ PATH: "/bin:/usr/bin:/usr/local/bin"
+
+# -------------------------------------------------------------
+# Routes
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+routes:
+ /status: echo 200 ALIVE
+
+ /motd: motd
+ /uptime: uptime
+ /uname: uname -a
+
+ /fortune: fortune
+ /fortune/bofh: fortune bofh
+ /fortune/epictetus: fortune epictetus
+ /fortune/freebsd: fortune freebsd-classic
+ /fortune/futurama: fortune futurama
+ /fortune/tips: fortune freebsd-tips
+
+ /metrics/ccache:
+ command: ccache-metrics
+ mime_type: application/openmetrics-text
+
+ /version: cat /etc/os-release
+ /version/runtime: freebsd-version -r
+ /version/kernel: freebsd-version -k
+ /version/userland: freebsd-version -u
diff --git a/roles/devserver/api-exec/files/rc/api_exec b/roles/devserver/api-exec/files/rc/api_exec
new file mode 100644
--- /dev/null
+++ b/roles/devserver/api-exec/files/rc/api_exec
@@ -0,0 +1,67 @@
+#!/bin/sh
+
+# PROVIDE: api_exec
+# REQUIRE: DAEMON
+# KEYWORD: shutdown
+
+# -------------------------------------------------------------
+# API :: api-exec
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# License: Trivial work, not eligible to copyright
+# Source file: roles/devserver/api-exec/files/rc/api_exec
+# -------------------------------------------------------------
+#
+# <auto-generated>
+# This file is managed by our rOPS SaltStack repository.
+#
+# Changes to this file may cause incorrect behavior
+# and will be lost if the state is redeployed.
+# </auto-generated>
+
+# Add the following lines to /etc/rc.conf.local or /etc/rc.conf
+# to enable this service:
+# api_exec_enable (bool): Set it to YES to enable api_exec.
+# Default is "NO"
+# api_exec_user (user): Set user to run api_exec
+# Default is "nobody".
+# api_exec_config (path): The configuration file to use.
+# Default is "/usr/local/etc/api-exec.conf".
+# api_exec_venv (path): Path to Python virtual environment to use.
+# Default is "/srv/api-exec/venv".
+# api_exec_logfile (path): Path to the service log.
+# Default is "/var/log/api-exec.log".
+
+. /etc/rc.subr
+
+name="api_exec"
+rcvar="${name}_enable"
+
+load_rc_config $name
+
+: ${api_exec_enable:="NO"}
+: ${api_exec_user:="nobody"}
+: ${api_exec_config:="/usr/local/etc/api-exec.conf"}
+: ${api_exec_venv:="/srv/api-exec/venv"}
+: ${api_exec_logfile:="/var/log/api-exec.log"}
+
+pidfile="/var/run/api-exec.pid"
+command="${api_exec_venv}/bin/uwsgi"
+command_args="--master --yaml ${api_exec_config} --daemonize2=${api_exec_logfile}"
+
+sig_stop="INT"
+start_precmd=api_exec_startprecmd
+stop_postcmd=api_exec_stop_postcmd
+
+api_exec_startprecmd()
+{
+ touch ${pidfile} ${api_exec_logfile}
+ chown ${api_exec_user} ${pidfile} ${api_exec_logfile}
+}
+
+api_exec_stop_postcmd()
+{
+ rm -f ${pidfile}
+}
+
+run_rc_command "$1"
diff --git a/roles/devserver/api-exec/files/rc/api_exec.conf b/roles/devserver/api-exec/files/rc/api_exec.conf
new file mode 100644
--- /dev/null
+++ b/roles/devserver/api-exec/files/rc/api_exec.conf
@@ -0,0 +1,16 @@
+# -------------------------------------------------------------
+# API :: api-exec
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# License: Trivial work, not eligible to copyright
+# Source file: roles/devserver/api-exec/files/rc/api_exec.conf
+# -------------------------------------------------------------
+#
+# <auto-generated>
+# This file is managed by our rOPS SaltStack repository.
+#
+# Changes to this file may cause incorrect behavior
+# and will be lost if the state is redeployed.
+# </auto-generated>
+
+api_exec_enable="YES"
diff --git a/roles/devserver/api-exec/files/server.py b/roles/devserver/api-exec/files/server.py
new file mode 100755
--- /dev/null
+++ b/roles/devserver/api-exec/files/server.py
@@ -0,0 +1,149 @@
+#!/usr/bin/env python3
+
+# -------------------------------------------------------------
+# API to execute commands
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# Description: Run commands
+# License: BSD-2-Clause
+# Source file: roles/devserver/api-exec/files/server.py
+# -------------------------------------------------------------
+#
+# <auto-generated>
+# This file is managed by our rOPS SaltStack repository.
+#
+# Changes to this file may cause incorrect behavior
+# and will be lost if the state is redeployed.
+# </auto-generated>
+
+
+import os
+import subprocess
+import sys
+
+from flask import Flask, Response, abort
+import yaml
+
+
+# -------------------------------------------------------------
+# Parse configuration
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+DEFAULT_CONFIGURATION_FILE = "/usr/local/etc/api-exec.conf"
+DEFAULT_MIME_TYPE = "text/plain"
+
+
+def load_config(config_file=None):
+ if config_file is None:
+ config_file = DEFAULT_CONFIGURATION_FILE
+
+ with open(config_file, "r") as file:
+ config = yaml.safe_load(file)
+
+ config["routes"] = {
+ key: ApiExecRoute.parse_config_entry(value)
+ for key, value in config["routes"].items()
+ }
+
+ return config
+
+
+def set_environment(env):
+ for key, value in env.items():
+ os.environ[key] = value
+
+
+class ApiExecRoute:
+ def __init__(self, command, mime_type):
+ self.command = command
+ self.mime_type = mime_type
+
+ @staticmethod
+ def parse_config_entry(entry):
+ if type(entry) is str:
+ return ApiExecRoute(entry, DEFAULT_MIME_TYPE)
+
+ mime_type = entry.get("mime_type", DEFAULT_MIME_TYPE)
+ return ApiExecRoute(entry["command"], mime_type)
+
+
+# -------------------------------------------------------------
+# Run commands
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+def execute_command(command):
+ result = subprocess.run(
+ command.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
+ )
+
+ return result.stdout, result.stderr, result.returncode == 0
+
+
+# -------------------------------------------------------------
+# Build Flask application
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+def initialize_app():
+ app = Flask(__name__)
+
+ app_config = load_config(os.environ.get("APP_EXEC_CONFIG_PATH"))
+ if not app_config:
+ print("Can't load configuration", file=sys.stderr)
+ sys.exit(1)
+
+ set_environment(app_config.get("env", {}))
+ app.config["routes"] = app_config["routes"]
+
+ return app
+
+
+app = initialize_app()
+
+
+# -------------------------------------------------------------
+# Requests
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+@app.route("/<route>", methods=["GET"])
+def get_level1(route):
+ return handle_command_request("/" + route)
+
+
+@app.route("/<route>/<subroute>", methods=["GET"])
+def get_level2(route, subroute):
+ return handle_command_request(f"/{route}/{subroute}")
+
+
+def handle_command_request(route):
+ args = app.config.get("routes", {}).get(route, None)
+
+ if not args:
+ return abort(404, description=f"Route '{route}' not found")
+
+ output, error, is_ok = execute_command(args.command)
+
+ if not is_ok:
+ if error is None or error.strip() == "":
+ description = "Error running command"
+ else:
+ description = f"Error running command: {error}"
+ return abort(500, description=description)
+
+ resp = Response(output, mimetype=args.mime_type)
+ resp.headers["Access-Control-Allow-Origin"] = "*"
+ resp.headers["X-API-Exec-Command"] = args.command
+
+ return resp
+
+
+# -------------------------------------------------------------
+# Application entry point
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+if __name__ == "__main__":
+ app.run(host="127.0.0.1", port=8000)
diff --git a/roles/devserver/init.sls b/roles/devserver/api-exec/init.sls
copy from roles/devserver/init.sls
copy to roles/devserver/api-exec/init.sls
--- a/roles/devserver/init.sls
+++ b/roles/devserver/api-exec/init.sls
@@ -1,18 +1,11 @@
# -------------------------------------------------------------
-# Salt — Provision a development server
+# API :: api-exec
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Project: Nasqueron
-# Created: 2017-10-20
# License: Trivial work, not eligible to copyright
# -------------------------------------------------------------
include:
- - .datacube
- - .dns
- - .mail
- - .pkg
- - .userland-software
- - .userland-home
- - .poudriere
- - .webserver-home
- - .webserver-wwwroot51
+ - .software
+ - .config
+ - .service
diff --git a/roles/devserver/api-exec/service.sls b/roles/devserver/api-exec/service.sls
new file mode 100644
--- /dev/null
+++ b/roles/devserver/api-exec/service.sls
@@ -0,0 +1,21 @@
+# -------------------------------------------------------------
+# API :: api-exec
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# License: Trivial work, not eligible to copyright
+# -------------------------------------------------------------
+
+{% from "map.jinja" import dirs, services with context %}
+
+{% if services["manager"] == "rc" %}
+
+{{ dirs.etc }}/rc.d/api_exec:
+ file.managed:
+ - source: salt://roles/devserver/api-exec/files/rc/api_exec
+ - mode: 755
+
+/etc/rc.conf.d/api_exec:
+ file.managed:
+ - source: salt://roles/devserver/api-exec/files/rc/api_exec.conf
+
+{% endif %}
diff --git a/roles/devserver/api-exec/software.sls b/roles/devserver/api-exec/software.sls
new file mode 100644
--- /dev/null
+++ b/roles/devserver/api-exec/software.sls
@@ -0,0 +1,33 @@
+# -------------------------------------------------------------
+# API :: api-exec
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# License: Trivial work, not eligible to copyright
+# -------------------------------------------------------------
+
+# -------------------------------------------------------------
+# Source code
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+/srv/api-exec/src/server.py:
+ file.managed:
+ - source: salt://roles/devserver/api-exec/files/server.py
+ - makedirs: True
+ - mode: 755
+
+# -------------------------------------------------------------
+# Python virtual environment
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+/srv/api-exec/venv:
+ file.directory:
+ - user: deploy
+
+api_exec_virtualenv:
+ cmd.run:
+ - name: |
+ python3 -m venv /srv/api-exec/venv && \
+ . /srv/api-exec/venv/bin/activate && \
+ pip install Flask PyYAML uwsgi
+ - creates: /srv/api-exec/venv/bin/activate
+ - runas: deploy
diff --git a/roles/devserver/init.sls b/roles/devserver/init.sls
--- a/roles/devserver/init.sls
+++ b/roles/devserver/init.sls
@@ -14,5 +14,9 @@
- .userland-software
- .userland-home
- .poudriere
+
+ # Needs userland-software
+ - .api-exec
+
- .webserver-home
- .webserver-wwwroot51
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Nov 18, 18:56 (20 h, 8 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2250310
Default Alt Text
D3494.id9003.diff (13 KB)
Attached To
Mode
D3494: Serve ccache metrics and other utilities through api-exec
Attached
Detach File
Event Timeline
Log In to Comment