aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore21
-rw-r--r--README.md64
-rw-r--r--extra/Makefile134
-rw-r--r--extra/fifconfig.conf.apache62
-rw-r--r--extra/fifconfig.conf.nginx13
-rwxr-xr-xextra/fifconfig.init174
-rw-r--r--extra/fifconfig.service21
-rw-r--r--extra/fifconfig.spec156
-rw-r--r--extra/fifconfig.sysusers3
-rw-r--r--extra/pip-helper.sh37
-rwxr-xr-xfifconfig.bin11
-rw-r--r--fifconfig.conf.example1
-rw-r--r--fifconfig.py232
-rw-r--r--fifconfig.wsgi.ini.dev17
-rw-r--r--fifconfig.wsgi.ini.example17
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
bgstack15