diff options
-rw-r--r-- | .gitignore | 21 | ||||
-rw-r--r-- | README.md | 64 | ||||
-rw-r--r-- | extra/Makefile | 134 | ||||
-rw-r--r-- | extra/fifconfig.conf.apache | 62 | ||||
-rw-r--r-- | extra/fifconfig.conf.nginx | 13 | ||||
-rwxr-xr-x | extra/fifconfig.init | 174 | ||||
-rw-r--r-- | extra/fifconfig.service | 21 | ||||
-rw-r--r-- | extra/fifconfig.spec | 156 | ||||
-rw-r--r-- | extra/fifconfig.sysusers | 3 | ||||
-rw-r--r-- | extra/pip-helper.sh | 37 | ||||
-rwxr-xr-x | fifconfig.bin | 11 | ||||
-rw-r--r-- | fifconfig.conf.example | 1 | ||||
-rw-r--r-- | fifconfig.py | 232 | ||||
-rw-r--r-- | fifconfig.wsgi.ini.dev | 17 | ||||
-rw-r--r-- | fifconfig.wsgi.ini.example | 17 |
15 files changed, 963 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..69361c7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.pyc +*.pyo +*.egg-info +*.db +build +dist +.DS_Store +.env +env +.*.swp +log/ +var/ +*.pid +*.conf +*.ini +debian/.debhelper/ +debian/*debhelper* +debian/files +debian/*.substvars +debian/fifconfig/ +.pc diff --git a/README.md b/README.md new file mode 100644 index 0000000..32495eb --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +# README for fifconfig +## Metadata +Filename: README.md +Location: https://gitlab.com/bgstack15/fifconfig +Author: bgstack15 +Startdate: 2022-03-15 +Title: Flask-ifconfig, or Http client diagnostic webapp +Purpose: Replicate http://ifconfig.me +History: +Usage: +Reference: See References heading +Improve: +Dependencies: See Dependencies heading +Documentation: + +## Upstream +[gitlab](https://gitlab.com/bgstack15/fifconfig) +[author's git](https://bgstack15.ddns.net/cgit/fifconfig) + +## Features + +* Provide different output type based on `Accept` header or url parameter. Options include: + * `application/json` or `?json` + * `text/html` or `?html` + * `application/xml` or `?xml` + * `text/plain` or `?text` +* Display IP address as viewed by the web server, or else the first entry of `HTTP_X_FORWARDED_FOR`. + +## Using fifconfig +Visit the application. + + curl -L https://bgstack15.ddns.net/ifconfig/?json | jq + +### Installing + +You can use flask for development, and uwsgi for production. + +### Instructions + +Configure the application with these two files, based on the `.example` files available in the source code: + +* fifconfig.conf +* fifconfig.wsgi.ini + +#### Development +Run server in development mode. + + FLASK_APP=fifconfig.py FLASK_DEBUG=True flask run --host='0.0.0.0' + +#### Production +Run the server in a full wsgi environment for the cleanup timer to operate. + + ./fifconfig.bin + +The html responses include links to the various single-field pages, unless you add a parameter `?nolinks`. These links depend on any reverse-proxy servers adding themselves correctly to header `X-Forwarded-For`. + +## Alternatives +This project was a ripoff of love of [http://ifconfig.me](http://ifconfig.me). + +## References + +1. [flask.Request — Flask API](https://tedboy.github.io/flask/generated/generated/flask.Request.html) +2. [my stackbin.py project](https://bgstack15.ddns.net/cgit/stackbin/tree/stackbin.py) +3. [dicttoxml - PyPI](https://pypi.org/project/dicttoxml) diff --git a/extra/Makefile b/extra/Makefile new file mode 100644 index 0000000..6106c24 --- /dev/null +++ b/extra/Makefile @@ -0,0 +1,134 @@ +# File: Makefile for fifconfig +# Location: fifconfig source package +# Author: bgstack15 +# Startdate: 2020-12-30 +# Title: Makefile for fifconfig source package +# Purpose: To use traditional Unix make utility +# History: +# Usage: +# Reference: +# stackbin Makefile +# Improve: +# add man page? +# Document: +# Dependencies: +# build-devuan: + +APPNAME = fifconfig +APPVERSION = 0.0.1 +SRCDIR = $(CURDIR)/..# because Makefile is in extra/ inside this repo +prefix = /usr +SYSCONFDIR = $(DESTDIR)/etc +DEFAULTDIR = $(DESTDIR)/etc/sysconfig# for debian use '$(DESTDIR)/etc/default' +LIBEXECDIR = $(DESTDIR)$(prefix)/libexec +SHAREDIR = $(DESTDIR)$(prefix)/share +DOCDIR = $(SHAREDIR)/doc/$(APPNAME) +APPDIR = $(SHAREDIR)/$(APPNAME) +APPVARDIR = $(DESTDIR)/var/$(APPNAME) +MANDIR = $(SHAREDIR)/man +SYSVDIR = $(SYSCONFDIR)/init.d +SYSDDIR = $(DESTDIR)$(prefix)/lib/systemd/system +LOGDIR = $(DESTDIR)/var/log/fifconfig +APACHEDIR = $(SYSCONFDIR)/httpd/conf.d# for debian use '$(SYSCONFDIR)/apache2/sites-available' +NGINXDIR = $(SYSCONFDIR)/nginx/default.d +CRONDIR = $(SYSCONFDIR)/cron.d +SBINDIR = $(DESTDIR)$(prefix)/sbin + +# variables for deplist +DEPTYPE = dep +SEPARATOR = , + +awkbin :=$(shell which awk) +chmodbin :=$(shell which chmod) +cpbin :=$(shell which cp) +echobin :=$(shell which echo) +falsebin :=$(shell which false) +findbin :=$(shell which find) +grepbin :=$(shell which grep) +gzipbin :=$(shell which gzip) +installbin :=$(shell which install) +rmbin :=$(shell which rm) +rmdirbin :=$(shell which rmdir) +sedbin :=$(shell which sed) +sortbin :=$(shell which sort) +truebin :=$(shell which true) +uniqbin :=$(shell which uniq) +xargsbin :=$(shell which xargs) + +with_apache ?= NO +with_nginx ?= YES +with_init ?= YES +with_systemd ?= NO +with_pip_helper ?= NO + +all: + -@echo "Nothing to build." && ${truebin} + +install: install_files + +.PHONY: clean install install_files uninstall list deplist deplist_opts + +list: + @$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | ${awkbin} -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | ${sortbin} | ${grepbin} -E -v -e '^[^[:alnum:]]' -e '^$@$$' + +deplist: + @# deplist 2020-04-18 input must be comma separated + @# DEPTYPE( dep , rec , sug ) for depends, recommends, or suggests + @if test -z "${DISTRO}" ; then ${echobin} "Please run \`make deplist\` with DISTRO= one of: `make deplist_opts 2>&1 1>/dev/null | ${xargsbin}`. Aborted." 1>&2 ; exit 1 ; fi + @if ! ${echobin} "${DEPTYPE}" | grep -qE "^(dep|rec|sug)$$" ; then ${echobin} "Please run \`make deplist\` with DEPTYPE= one of: dep, rec, sug. Undefined will use \`dep\`. Aborted." 1>&2 ; exit 1; fi + @${grepbin} -h --exclude-dir='doc' -riIE "\<${DEPTYPE}-" ${SRCDIR} | ${awkbin} -v "domain=${DISTRO}" -v "deptype=${DEPTYPE}" 'tolower($$2) ~ deptype"-"domain {$$1="";$$2="";print}' | tr ',' '\n' | ${sortbin} | ${uniqbin} | ${sedbin} -r -e 's/^\s*//' -e "s/\s*\$$/${SEPARATOR}/" | ${xargsbin} + +deplist_opts: + @# deplist_opts 2020-04-18 find all available dependency domains + @${grepbin} -h -o -riIE '\<(dep|rec|sug)-[^\ :]+:' ${SRCDIR} | ${sedbin} -r -e 's/(dep|rec|sug)-//;' -e 's/:$$//;' | ${sortbin} | ${uniqbin} 1>&2 + +install_files: + ${installbin} -m0755 -d ${LOGDIR} ${APPVARDIR} ${APPDIR}/static ${APPDIR}/templates \ + ${DOCDIR} ${SBINDIR} ${SYSCONFDIR} ${LIBEXECDIR}/${APPNAME} + ${installbin} -m0755 -t ${SBINDIR} ${SRCDIR}/${APPNAME}.bin + ${installbin} -m0644 -t ${LIBEXECDIR}/${APPNAME} ${SRCDIR}/${APPNAME}.py ${SRCDIR}/${APPNAME}_auth.py + ${installbin} -m0644 ${SRCDIR}/${APPNAME}.conf.example ${SYSCONFDIR}/${APPNAME}.conf + ${installbin} -m0644 ${SRCDIR}/${APPNAME}.wsgi.ini.example ${SYSCONFDIR}/${APPNAME}.wsgi.ini + ${installbin} -m0644 -t ${DOCDIR} ${SRCDIR}/*.md + ${installbin} -m0644 -t ${APPDIR}/static ${SRCDIR}/static/* + ${installbin} -m0644 -t ${APPDIR}/templates ${SRCDIR}/templates/* +ifeq ($(with_apache),YES) + ${installbin} -m0755 -d ${APACHEDIR} + ${installbin} -m0644 ${SRCDIR}/extra/${APPNAME}.conf.apache ${APACHEDIR}/${APPNAME}.conf +endif +ifeq ($(with_nginx),YES) + ${installbin} -m0755 -d ${NGINXDIR} + ${installbin} -m0644 ${SRCDIR}/extra/${APPNAME}.conf.nginx ${NGINXDIR}/${APPNAME}.conf +endif +ifeq ($(with_init),YES) + ${installbin} -m0755 -d ${SYSVDIR} + ${installbin} -m0755 ${SRCDIR}/extra/${APPNAME}.init ${SYSVDIR}/${APPNAME} +endif +ifeq ($(with_systemd),YES) + ${installbin} -m0755 -d ${SYSDDIR} + ${installbin} -m0644 ${SRCDIR}/extra/fifconfig.service -t ${SYSDDIR} +endif +ifeq ($(with_pip_helper),YES) + ${installbin} -m0755 -t ${LIBEXECDIR}/${APPNAME}/ ${SRCDIR}/extra/pip-helper.sh +endif + +uninstall: + @${echobin} SRCDIR=${SRCDIR} + ${rmbin} -f ${APPDIR}/${APPNAME}.* \ + ${DOCDIR}/* \ + ${APPDIR}/static/* \ + ${APPDIR}/templates/* \ + ${LIBEXECDIR}/${APPNAME}/* \ + ${SBINDIR}/${APPNAME}.bin \ + 1>/dev/null 2>&1 || : + ${rmbin} -f ${APACHEDIR}/${APPNAME}.conf || : + ${rmbin} -f ${SYSVDIR}/${APPNAME} || : + + # remove all installed directories that are now blank. + ${rmdirbin} ${APPVARDIR} ${APPDIR}/static \ + ${SYSVDIR} ${APACHEDIR} ${DOCDIR} \ + ${LIBEXECDIR}/${APPNAME} ${LOGDIR} 2>/dev/null || : + ${rmdirbin} ${APPDIR} 2>/dev/null || : + +clean: + -@${echobin} "target $@ not implemented yet! Gotta say unh." && ${falsebin} diff --git a/extra/fifconfig.conf.apache b/extra/fifconfig.conf.apache new file mode 100644 index 0000000..2587131 --- /dev/null +++ b/extra/fifconfig.conf.apache @@ -0,0 +1,62 @@ +# Apache example config for fifconfig application +# Needs setsebool -P http_can_network_connect 1 +# vim:set syntax=apache ts=3 sw=3 sts=3 sr et: +<VirtualHost *:80> + + ServerName d2-03a.ipa.example.com + + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html + + #LogLevel info ssl:warn + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + # OPTION 1: send to https + # force https for this path + RewriteEngine On + RewriteCond %{HTTPS} !=on + RewriteCond %{HTTP_HOST} !^(localhost|127.0.0.1) + RewriteRule ^/fifconfig(.*) https://%{SERVER_NAME}/fifconfig$1 [R,L] + + # OPTION 2: Just use unencrypted + #ProxyPass /fifconfig http://localhost:4681/ + #ProxyPassReverse /fifconfig http://localhost:4681/ + #<Location /fifconfig> + # RequestHeader append X-Forwarded-Prefix "/fifconfig" + # RequestHeader set X-Forwarded-Proto "http" + #</Location> + +</VirtualHost> + +# To use OPTION 2 above, just disable this whole 443 virtualhost. +<VirtualHost *:443> + ServerName d2-03a.ipa.example.com + + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html + + #LogLevel info ssl:warn + + ErrorLog ${APACHE_LOG_DIR}/ssl-error.log + CustomLog ${APACHE_LOG_DIR}/ssl-access.log combined + + SSLEngine on + SSLProtocol all -SSLv2 -SSLv3 + SSLHonorCipherOrder on + SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA !RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS" + + SSLCertificateFile /etc/ssl/private/https-d2-03a.ipa.example.com.pem + SSLCertificateKeyFile /etc/ssl/private/https-d2-03a.ipa.example.com-nopw.key + + ProxyPass /fifconfig http://localhost:4680/ + ProxyPassReverse /fifconfig http://localhost:4680/ + <Location /fifconfig> + # a2enmod headers. These are extra ones that are not provided by Apache natively. + RequestHeader set X-Forwarded-Proto "https" + # This header is not required to be set manually. The ProxyPass orand Location directive already provide it! + #RequestHeader append X-Forwarded-Prefix "/fifconfig" + </Location> + +</VirtualHost> diff --git a/extra/fifconfig.conf.nginx b/extra/fifconfig.conf.nginx new file mode 100644 index 0000000..a626430 --- /dev/null +++ b/extra/fifconfig.conf.nginx @@ -0,0 +1,13 @@ +# Nginx example config for fifconfig application +# Needs setsebool -P http_can_network_connect 1 +location /fifconfig/ { + #proxy_redirect off; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Script-Name /fifconfig; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Prefix "/fifconfig"; + proxy_pass http://localhost:4681/; +} + diff --git a/extra/fifconfig.init b/extra/fifconfig.init new file mode 100755 index 0000000..69fb39b --- /dev/null +++ b/extra/fifconfig.init @@ -0,0 +1,174 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: fifconfig +# Required-Start: $local_fs $network $remote_fs $syslog +# Required-Stop: $local_fs $network $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: File Upoad and Storage Service +# Description: Python3 Flask application that presents a basic file hosting service +### END INIT INFO + +# Author: B. Stack <bgstack15@gmail.com> + +# Do NOT "set -e" + +# PATH should only include /usr/* if it runs after the mountnfs.sh script +DAEMON_ARGS="" +DAEMON=/usr/sbin/fifconfig.bin +DESC="fifconfig" +NAME=fifconfig +PATH=/sbin:/usr/sbin:/bin:/usr/bin +PIDFILE2=/var/$NAME/$NAME-wsgi.pid +PIDFILE=/var/run/$NAME.pid +SCRIPTNAME=/etc/init.d/$NAME +USER=fifconfig +WSGIBIN=uwsgi_python39 +export FIFCONFIG_WSGI_INI=/etc/fifconfig.wsgi.ini +export FIFCONFIG_CONF=/etc/fifconfig.conf + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +# Load the VERBOSE setting and other rcS variables +. /lib/init/vars.sh + +# Define LSB log_* functions. +# Depend on lsb-base (>= 3.2-14) to ensure that this file is present +# and status_of_proc is working. +. /lib/lsb/init-functions + +# +# Function that starts the daemon/service +# +do_start() +{ + # Return + # 0 if daemon has been started + # 1 if daemon was already running + # 2 if daemon could not be started + #start-stop-daemon --start --quiet --chuid $USER --group $USER --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ + # || return 1 + #start-stop-daemon --start --quiet --chuid $USER --group $USER --background --pidfile $PIDFILE --exec $DAEMON -- \ + # $DAEMON_ARGS \ + # || return 2 + #su $USER -c "$DAEMON" & + # The above code will not work for interpreted scripts, use the next + # six lines below instead (Ref: #643337, start-stop-daemon(8) ) + start-stop-daemon --start --make-pidfile --pidfile $PIDFILE \ + --chuid $USER --group $USER \ + --startas $DAEMON \ + --name $WSGIBIN --test > /dev/null \ + || return 1 + start-stop-daemon --start --background --make-pidfile --pidfile $PIDFILE --startas $DAEMON \ + --chuid $USER --group $USER \ + --name $WSGIBIN -- $DAEMON_ARGS \ + || return 2 + + # Add code here, if necessary, that waits for the process to be ready + # to handle requests from services started subsequently which depend + # on this one. As a last resort, sleep for some time. +} + +# +# Function that stops the daemon/service +# +do_stop() +{ + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + #start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --user $USER --pidfile $PIDFILE + /usr/bin/$WSGIBIN --stop $PIDFILE2 2>/dev/null + RETVAL="$?" + [ "$RETVAL" = 2 ] && return 2 + # Wait for children to finish too if this is a daemon that forks + # and if the daemon is only ever run from this initscript. + # If the above conditions are not satisfied then add some other code + # that waits for the process to drop all resources that could be + # needed by services started subsequently. A last resort is to + # sleep for some time. + start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --user $USER --pidfile $PIDFILE + [ "$?" = 2 ] && return 2 + # Many daemons don't delete their pidfiles when they exit. + rm -f $PIDFILE + return "$RETVAL" +} + +# +# Function that sends a SIGHUP to the daemon/service +# +do_reload() { + # + # If the daemon can reload its configuration without + # restarting (for example, when it is sent a SIGHUP), + # then implement that here. + # + start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $WSGIBIN + return 0 +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + status) + status_of_proc -p $PIDFILE "$DAEMON" "$NAME" && exit 0 || exit $? + ;; + #reload|force-reload) + # + # If do_reload() is not implemented then leave this commented out + # and leave 'force-reload' as an alias for 'restart'. + # + #log_daemon_msg "Reloading $DESC" "$NAME" + #do_reload + #log_end_msg $? + #;; + restart|force-reload) + # + # If the "reload" option is implemented then remove the + # 'force-reload' alias + # + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + ;; + *) + #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 + echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 + exit 3 + ;; +esac + +: diff --git a/extra/fifconfig.service b/extra/fifconfig.service new file mode 100644 index 0000000..fe56e8f --- /dev/null +++ b/extra/fifconfig.service @@ -0,0 +1,21 @@ +[Unit] +Description=fifconfig http client diagnostic webapp +Wants=network-online.target +After=network.target syslog.target + +[Service] +Environment=FIFCONFIG_WSGI_INI=/etc/fifconfig.wsgi.ini +Environment=FIFCONFIG_CONF=/etc/fifconfig.conf +WorkingDirectory=/var/fifconfig +User=fifconfig +Group=fifconfig +Type=simple +ExecStart=/usr/sbin/fifconfig.bin +TimeoutStartSec=120 +ExecStop=/usr/sbin/uwsgi --stop /var/fifconfig/fifconfig-wsgi.pid +RestartSec=15 +Restart=always +KillSignal=SIGINT + +[Install] +WantedBy=multi-user.target diff --git a/extra/fifconfig.spec b/extra/fifconfig.spec new file mode 100644 index 0000000..3819a8b --- /dev/null +++ b/extra/fifconfig.spec @@ -0,0 +1,156 @@ +# File: fifconfig.spec +# Location: fifconfig package +# Author: bgstack15 +# SPDX-License-Identifier: CC-BY-SA-4.0 +# Startdate: 2021-01-05 +# Title: Rpm spec for fifconfig package +# Purpose: Provide build instructions for CentOS rpm for package +# History: +# Usage: +# Reference: +# fuss.spec +# Improve: +# Documentation: +# Dependencies: + +# Tunables +# If you set this to 1, use the systemd-rpm-macros functionality described at https://docs.fedoraproject.org/en-US/packaging-guidelines/UsersAndGroups/ +%global with_systemd_usercreate 0 +%global pythonver python36 + +# Fedora defaults +%if 0%{?fedora} +%global with_systemd_usercreate 1 +%global pythonver python3 +%endif + +%global _appvardir %{?_localstatedir}%{!?_localstatedir:/var}/fifconfig +%global _appdir %{?_datarootdir}%{!?_datarootdir:%{_prefix}/share}/fifconfig +%global _user fifconfig + +%define devtty "/dev/null" +%define debug_package %{nil} +%global _python_bytecompile_errors_terminate_build 0 + +Summary: Http client diagnostic webapp +Name: fifconfig +Version: 0.0.1 +Release: 1 +License: GPL 3.0 +Source0: https://gitlab.com/bgstack15/%{name}/-/archive/master/%{name}-master.tar.gz +%if 0%{?with_systemd_usercreate} +Source1: extra/%{name}.sysusers +%endif +#%%if ! 0%%{?fedora} +#Patch1: extra/%%{name}-el7.patch +#%%endif +URL: https://bgstack15.ddns.net/ +Packager: B. Stack <bgstack15@gmail.com> +Requires: %{pythonver}-flask +Requires: %{pythonver}-xmltodict +Requires: uwsgi-plugin-%{pythonver} +Requires: uwsgi-logger-file +%if 0%{?fedora} +BuildRequires: make +# Fedora needs pip helper here +#Requires: %{pythonver}-json2html +%endif +# Mandatory pip3 requirements: pytimeparse, flask-sqlalchemy +#BuildRequires: txt2man +%if 0%{?with_systemd_usercreate} +BuildRequires: systemd-rpm-macros +%endif +%if 0%{?fedora} || 0%{?rhel} >= 8 +Suggests: nginx +%endif +Buildarch: noarch + +%description +Stackbin is a flask-based pastebin implementation. + +%prep +%setup -q -c %{name} +test -d "%{name}" && cd "%{name}" ; test -d "%{name}-"* && cd "%{name}-"* ; +#%%if ! 0%%{?fedora} +#%%patch1 -p1 +#%%endif + +%build +export srcdir="extra" +test -d "%{name}" && cd "%{name}" ; test -d "%{name}-"* && cd "%{name}-"* ; +%make_build -C "${srcdir}" + +%install +export srcdir="extra" +test -d "%{name}" && cd "%{name}" ; test -d "%{name}-"* && cd "%{name}-"* ; +%make_install -C "${srcdir}" \ + with_systemd=YES \ + with_nginx=YES \ + with_apache=NO \ + with_init=NO \ +%if 0%{?fedora} + with_pip_helper=NO +%else + with_pip_helper=YES +%endif +%if 0%{?with_systemd_usercreate} +install -p -D -m 0644 %{SOURCE1} %{buildroot}%{_sysusersdir}/%{name}.conf +%endif +exit 0 + +%clean +rm -rf %{buildroot} + +%pre +# Reference: squid.spec +%if 0%{?with_systemd_usercreate} +%sysusers_create_compat %{SOURCE1} +%else +if ! getent group %{_user} 1>/dev/null 2>&1 ; +then + /usr/sbin/groupadd --system --gid 725 %{_user} +fi +if ! getent passwd %{_user} 1>/dev/null 2>&1 ; +then + /usr/sbin/useradd --system --gid 725 \ + --uid 725 --comment "fifconfig system user" \ + --home-dir %{_appvardir} --shell /sbin/nologin \ + %{_user} +fi +%endif +exit 0 + +%preun +%systemd_postun_with_restart %{name}.service + +%post +%systemd_post %{name}.service + +%postun +%systemd_postun_with_restart %{name}.service + +%files +%if 0%{?with_systemd_usercreate} +%{_sysusersdir}/%{name}.conf +%endif +%attr(0640, %{_user}, %{_user}) %config(noreplace) %{_sysconfdir}/%{name}.conf +%attr(0640, %{_user}, %{_user}) %config(noreplace) %{_sysconfdir}/%{name}.wsgi.ini +%attr(0640, %{_user}, %{_user}) %{_libexecdir}/%{name}/*.py +%attr(0755, %{_user}, %{_user}) %{_sbindir}/%{name}.bin +%attr(0644, %{_user}, %{_user}) %{_appdir}/static/* +%attr(0644, %{_user}, %{_user}) %{_appdir}/templates/* +%attr(0755, %{_user}, %{_user}) %dir %{_appdir}/static +%attr(0755, %{_user}, %{_user}) %dir %{_appdir}/templates +%attr(0755, %{_user}, %{_user}) %dir %{_appdir} +%attr(0750, %{_user}, %{_user}) %dir %{_appvardir} +%if ! 0%{?fedora} +%attr(0755, -, -) %{_libexecdir}/%{name}/pip-helper.sh +%endif +%attr(0644, root, root) %{?_unitdir}%{!?_unitdir:/usr/lib/systemd/system}/%{name}.service +%attr(0644, root, root) %{_sysconfdir}/nginx/default.d/%{name}.conf +%attr(0750, %{_user}, %{_user}) %dir %{?_localstatedir}%{!?_localstatedir:/var}/log/%{name} +%{_defaultdocdir}/%{name} + +%changelog +* Tue Feb 15 2022 B. Stack <bgstack15@gmail.com> - 0.0.1-1 +- Initial release diff --git a/extra/fifconfig.sysusers b/extra/fifconfig.sysusers new file mode 100644 index 0000000..813b698 --- /dev/null +++ b/extra/fifconfig.sysusers @@ -0,0 +1,3 @@ +# Part of fifconfig package +#Type Name ID GECOS Home directory Shell +u fifconfig - "fifconfig system user" /var/fifconfig /sbin/nologin diff --git a/extra/pip-helper.sh b/extra/pip-helper.sh new file mode 100644 index 0000000..3ac9a69 --- /dev/null +++ b/extra/pip-helper.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# This file is part of the fifconfig package. +# It is designed to be run as root, and it will run the requisite pip commands for the discovered environment. + +contents="$( awk -F'=' '/ID=|VERSION_ID/{print $2}' /etc/os-release 2>/dev/null | sed -r -e 's/"//g;' )" +id="$( echo "${contents}" | sed -n -e '1p' )" +version_id="$( echo "${contents}" | sed -n -e '2p' )" + +echo "Any parameters sent to this script ${0} will be added to the list of packages to install." +piplist="${1}" + +if echo "${id}" | grep -qiE 'rhel|centos' ; +then + #if echo "${version_id}" | grep -qiE '7' ; + #then + piplist="${piplist} json2html" + #fi +elif echo "${id}" | grep -qiE 'fedora' ; +then + piplist="${piplist} json2html" +elif echo "${id}" | grep -qiE 'devuan|debian' ; +then + piplist="${piplist} json2html" +else + echo "Unknown os from /etc/os-release. Please investigate what pip3" 1>&2 + echo "packages are required for this OS release and share it with upstream." 1>&2 + echo "Aborted." 1>&2 + exit 1 +fi + +if test -n "${piplist}" ; +then + echo "Will try to serially install with pip these packages: ${piplist}" + for word in ${piplist} ; do + su fifconfig -s /bin/sh -c "pip3 install --user ${word}" || exit 1 + done +fi diff --git a/fifconfig.bin b/fifconfig.bin new file mode 100755 index 0000000..ec77be6 --- /dev/null +++ b/fifconfig.bin @@ -0,0 +1,11 @@ +#!/bin/sh +# Reference: stackbin.bin from stackbin project +# Startdate: 2022-03-16 +test -z "${FIFCONFIG_WSGI_INI}" && { + thisscript="$( readlink -f "${0}" )" + FIFCONFIG_WSGI_INI="$( dirname "${thisscript}" )/$( basename "${thisscript}" | sed -r -e 's/\.bin$//;' ).wsgi.ini" +} +COMMAND="" +grep -qiE 'ID=.*(rhel|centos|fedora)' /etc/os-release && COMMAND="${COMMAND} uwsgi" || \ + COMMAND="${COMMAND:+${COMMAND} }uwsgi_python39" +${COMMAND} --ini "${FIFCONFIG_WSGI_INI}" diff --git a/fifconfig.conf.example b/fifconfig.conf.example new file mode 100644 index 0000000..a30950b --- /dev/null +++ b/fifconfig.conf.example @@ -0,0 +1 @@ +SOURCE_URL = "https://bgstack15.ddns.net/cgit/fifconfig/" diff --git a/fifconfig.py b/fifconfig.py new file mode 100644 index 0000000..6ff8d17 --- /dev/null +++ b/fifconfig.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python3 +# File: fifconfig.py +# Location: https://gitlab.com/bgstack15/fifconfig +# Author: bgstack15 +# SPDX-License-Identifier: GPL-3.0 +# Startdate: 2022-03-15 20:35 +# Title: Flask-ifconfig +# Purpose: Act similar to http://ifconfig.me but with flask, for my network testing usage +# History: +# Usage: +# Reference: +# https://tedboy.github.io/flask/generated/generated/flask.Request.html +# https://bgstack15.ddns.net/cgit/stackbin/tree/stackbin.py +# https://pypi.org/project/dicttoxml/ +# Improve: +# Dependencies: +# pip-devuan: json2html +# pip-centos7: json2html +# dep-devuan: python3-dicttoxml | python3-xmltodict +# dep-centos7: python36-xmltodict +# reverse-proxy configs that include X-Forwarded-Prefix headers. +# Documentation: see README.md + +from flask import Flask, request, jsonify, url_for + +app = Flask(__name__) +try: + app.config.from_pyfile('fifconfig.conf') +except: + pass + +def _make_dict_safe_for_text(response): + """ Mostly for converting the silly x-forwarded-for ImmutableList so that it does not show that class name. """ + # This works except for the silly "ImmutableList([" text output for that one 'via' item. + #response = { i:str(response[i]) for i in response } + # So instead of that beautiful oneliner, you get this stupid 9-line block. + gen = (i for i in response) + new_response = {} + for i in gen: + if "<class 'werkzeug.datastructures.ImmutableList'>" == str(type(response[i])): + #print(f"i {i} is a immutablelist") + new_response[i] = ','.join(response[i]) + else: + new_response[i] = str(response[i]) + return new_response + +def get_attribs( + request, + lia = False, + ia = False, + ua = False, + l = False, + re = False, + m = False, + e = False, + mt = False, + c = False, + xff = False +): + """ + Main function that builds the desired response dict + that will later be turned into json or html by the + various small @app.route functions. + """ + response = {} + r = request + rh = r.headers + if lia: + response['lastipaddress'] = r.remote_addr + if ia: + response['ipaddress'] = r.remote_addr + if r.access_route and len(r.access_route) > 0: + response['ipaddress'] = r.access_route[0] + if ua: + response['useragent'] = str(r.user_agent) + if l: + response['language'] = r.accept_languages + if re: + response['referer'] = r.referrer + if m: + response['method'] = r.method + if e: + response['encoding'] = r.accept_encodings + if mt: + response['mimetype'] = r.accept_mimetypes + if c: + response['charset'] = r.accept_charsets + if xff: + #response['x-forwarded-for'] = rh.get('X-Forwarded-For') or '' + response['x-forwarded-for'] = r.access_route + #'charset': rh.get('Accept-Charset') or '', + #'endpoint': request.endpoint + # via is the same as x-forwarded-for + #'via': r.access_route + print(f"DEBUG: dict is {response}") + return response + +def prepare_output(request, response): + """ + Used to customize the output to json, html, or text depending on what the client asked for. In order of most important to least important: + 1. request argument, i.e., '?json' + 2. Accept-Mimetypes header + """ + possible_formats = ['json','html','text','xml'] + _format = "text" + # priority two + for i in request.accept_mimetypes: + for j in i: + if 'application/xml' == j and _format != "html": + _format = "xml" + if 'application/json' == j: + _format = "json" + if 'application/xhtml+xml' == j or 'text/html' == j: + _format = "html" + if 'text/plain' == j: + _format = "text" + # priority one + if request.args: + if 'xml' in request.args: + _format = "xml" + if 'json' in request.args: + _format = "json" + if 'html' in request.args: + _format = "html" + if 'text' in request.args: + _format = "text" + if _format not in possible_formats: + print(f"DEBUG (prepare_output): how did format {_format} get defined?! Using text.") + _format = "text" + # main process + if "html" == _format: + print("Sending html") + if True: + from json2html import json2html + response = _make_dict_safe_for_text(response) + response = json2html.convert(json = response) + if not 'nolinks' in request.args: + prefix = '' + if 'HTTP_X_FORWARDED_PREFIX' in request.environ: + prefixes = request.environ['HTTP_X_FORWARDED_PREFIX'] + prefix = ''.join(prefixes.split(',')).replace(' ','') + response += f"<div style='font-size: 80%;'>" + response += f"<a href='{prefix}{url_for('ip')}'>ip</a> " + response += f"<a href='{prefix}{url_for('ua')}'>ua</a> " + response += f"<a href='{prefix}{url_for('lang')}'>lang</a> " + response += f"<a href='{prefix}{url_for('encoding')}'>encoding</a> " + response += f"<a href='{prefix}{url_for('mime')}'>mime</a> " + response += f"<a href='{prefix}{url_for('charset')}'>charset</a> " + response += f"<a href='{prefix}{url_for('forwarded')}'>forwarded</a> " + #response += f"<p>{request.environ}<p>" + response += f"<a href='{app.config['SOURCE_URL']}'>SOURCE</a>" + response += f"</div>" + return response + else: + print("Unable to load json2html, sending plain text.") + return str(response) + elif "xml" == _format: + print("Sending xml") + pretty = False + if request.args and 'pretty' in request.args: + pretty = True + try: + import xmltodict + response = xmltodict.unparse({'info':response}) + except: + try: + # This lib is objectively better but not available natively on CentOS 7 + print("Trying dicttoxml") + from dicttoxml import dicttoxml + response = dicttoxml(response,custom_root="info") + except: + print("Unable to load xmltodict, sending plain text.") + return str(response) + if pretty: + try: + from xml.dom.minidom import parseString + response = parseString(response).toprettyxml() + except: + pass + return response + elif "json" == _format: + print("Sending json") + return jsonify(response) + else: + # the only other option is text + print("Sending text") + response = _make_dict_safe_for_text(response) + new_response = "" + if 1 == len(response): + for i in response: + return response[i] + else: + for i in response: + new_response = new_response + '\n' + str(i) + ': ' + str(response[i]) + return new_response + +@app.route('/') +def root(): + response = get_attribs(request, lia = False, ia = True, ua = True, l = True, re = True, m = True, e = True, mt = True, c = True, xff = True) + response = prepare_output(request, response) + return response + +@app.route('/ip') +def ip(): + return prepare_output(request, get_attribs(request, ia = True)) + +@app.route('/ua') +def ua(): + return prepare_output(request, get_attribs(request, ua = True)) + +@app.route('/lang') +def lang(): + return prepare_output(request, get_attribs(request, l = True)) + +@app.route('/encoding') +def encoding(): + return prepare_output(request, get_attribs(request, e = True)) + +@app.route('/mime') +def mime(): + return prepare_output(request, get_attribs(request, mt = True)) + +@app.route('/charset') +def charset(): + return prepare_output(request, get_attribs(request, c = True)) + +@app.route('/forwarded') +def forwarded(): + return prepare_output(request, get_attribs(request, xff = True)) + +if __name__ == "__main__": + app.run() diff --git a/fifconfig.wsgi.ini.dev b/fifconfig.wsgi.ini.dev new file mode 100644 index 0000000..0f0930d --- /dev/null +++ b/fifconfig.wsgi.ini.dev @@ -0,0 +1,17 @@ +[uwsgi] +# CentOS 7 uwsgi needs "python36" added to this list. +#plugins = logfile, python36 +# Devuan Ceres does not. +plugins = logfile +http-socket = 0.0.0.0:4681 +wsgi-file = fifconfig.py +callable = app +touch-reload = fifconfig.py +touch-reload = fifconfig.conf +touch-reload = fifconfig.wsgi.ini +req-logger = file:log/req.log +# to get strftime format fields, you need double percent signs +logdate = "%%FT%%T" +logger = file:log/fifconfig.log +# the init script uses a different pidfile owned by root. +pidfile = var/fifconfig-wsgi.pid diff --git a/fifconfig.wsgi.ini.example b/fifconfig.wsgi.ini.example new file mode 100644 index 0000000..d7d1139 --- /dev/null +++ b/fifconfig.wsgi.ini.example @@ -0,0 +1,17 @@ +[uwsgi] +# CentOS 7 uwsgi needs "python36" added to this list. +plugins = logfile, python36 +# Devuan Ceres does not. +#plugins = logfile +http-socket = 127.0.0.1:4681 +wsgi-file = /usr/libexec/fifconfig/fifconfig.py +callable = app +touch-reload = /usr/libexec/fifconfig/fifconfig.py +touch-reload = /etc/fifconfig.conf +touch-reload = /etc/fifconfig.wsgi.ini +req-logger = file:/var/log/fifconfig/req.log +# to get strftime format fields, you need double percent signs +logdate = "%%FT%%T" +logger = file:/var/log/fifconfig/fifconfig.log +# the init script uses a different pidfile owned by root. +pidfile = /var/fifconfig/fifconfig-wsgi.pid |