aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--LICENSE28
-rw-r--r--README.md13
-rw-r--r--databases/py-tinydb/Makefile20
-rw-r--r--databases/py-tinydb/distinfo3
-rw-r--r--databases/py-tinydb/pkg-descr4
-rw-r--r--net-im/hommabot/Makefile39
-rw-r--r--net-im/hommabot/files/hommabot.in82
-rw-r--r--net-im/hommabot/files/pkg-message.in31
-rw-r--r--net-im/hommabot/pkg-descr1
-rw-r--r--net-im/taulubot/Makefile50
-rw-r--r--net-im/taulubot/distinfo3
-rw-r--r--net-im/taulubot/files/pkg-message.in30
-rw-r--r--net-im/taulubot/files/taulubot.in81
-rw-r--r--net-im/taulubot/pkg-descr1
-rw-r--r--sysutils/pylogsentinel/Makefile19
-rw-r--r--sysutils/pylogsentinel/distinfo3
-rw-r--r--sysutils/pylogsentinel/pkg-descr2
-rw-r--r--www/aggro/Makefile61
-rw-r--r--www/aggro/files/aggro.in65
-rw-r--r--www/aggro/pkg-descr2
-rw-r--r--www/diddle/Makefile58
-rw-r--r--www/diddle/files/diddle.in71
-rw-r--r--www/diddle/files/pkg-message.in31
-rw-r--r--www/diddle/pkg-descr1
-rw-r--r--www/expense-tracker/Makefile36
-rw-r--r--www/expense-tracker/files/expense_tracker.in56
-rw-r--r--www/expense-tracker/pkg-descr2
-rw-r--r--www/gdrive-photo-gallery/Makefile44
-rw-r--r--www/gdrive-photo-gallery/files/gdrive_photo_gallery.in61
-rw-r--r--www/gdrive-photo-gallery/pkg-descr2
-rw-r--r--www/pastebin/Makefile35
-rw-r--r--www/pastebin/Makefile.crates305
-rw-r--r--www/pastebin/distinfo0
-rw-r--r--www/pastebin/files/pastebin.in48
-rw-r--r--www/pastebin/pkg-descr1
-rw-r--r--www/py-ua-parser/Makefile20
-rw-r--r--www/py-ua-parser/distinfo3
-rw-r--r--www/py-ua-parser/pkg-descr2
-rw-r--r--www/py-user-agents/Makefile21
-rw-r--r--www/py-user-agents/distinfo3
-rw-r--r--www/py-user-agents/pkg-descr2
41 files changed, 1340 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f74cc6c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,28 @@
+BSD 3-Clause License
+
+Copyright (c) 2026, Jan T
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c01341a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,13 @@
+# Custom FreeBSD ports
+
+## Installation
+
+```bash
+# /etc/make.conf
+
+OVERLAYS=/usr/local/ports
+```
+
+## Author
+
+Jan Tuomi <<jan@jantuomi.fi>>
diff --git a/databases/py-tinydb/Makefile b/databases/py-tinydb/Makefile
new file mode 100644
index 0000000..f262a46
--- /dev/null
+++ b/databases/py-tinydb/Makefile
@@ -0,0 +1,20 @@
+PORTNAME= tinydb
+PORTVERSION= 4.8.2
+CATEGORIES= databases python
+MASTER_SITES= PYPI
+PKGNAMEPREFIX= ${PYTHON_PKGNAMEPREFIX}
+
+MAINTAINER= jan@jantuomi.fi
+COMMENT= Tiny document oriented database
+WWW= https://github.com/msiemens/tinydb
+
+LICENSE= MIT
+
+BUILD_DEPENDS= ${PYTHON_PKGNAMEPREFIX}poetry-core>0:devel/py-poetry-core@${PY_FLAVOR}
+
+USES= python:3.11+
+USE_PYTHON= pep517 autoplist
+
+NO_ARCH= yes
+
+.include <bsd.port.mk>
diff --git a/databases/py-tinydb/distinfo b/databases/py-tinydb/distinfo
new file mode 100644
index 0000000..e2a83f2
--- /dev/null
+++ b/databases/py-tinydb/distinfo
@@ -0,0 +1,3 @@
+TIMESTAMP = 1784811370
+SHA256 (tinydb-4.8.2.tar.gz) = f7dfc39b8d7fda7a1ca62a8dbb449ffd340a117c1206b68c50b1a481fb95181d
+SIZE (tinydb-4.8.2.tar.gz) = 32566
diff --git a/databases/py-tinydb/pkg-descr b/databases/py-tinydb/pkg-descr
new file mode 100644
index 0000000..a898a09
--- /dev/null
+++ b/databases/py-tinydb/pkg-descr
@@ -0,0 +1,4 @@
+TinyDB is a lightweight document oriented database optimized for your
+happiness. It's written in pure Python and has no external dependencies.
+The target are small apps that would be blown away by a SQL-DB or an
+external database server.
diff --git a/net-im/hommabot/Makefile b/net-im/hommabot/Makefile
new file mode 100644
index 0000000..1deaa05
--- /dev/null
+++ b/net-im/hommabot/Makefile
@@ -0,0 +1,39 @@
+PORTNAME= hommabot
+PORTVERSION= 2.0.0
+CATEGORIES= net-im
+
+MAINTAINER= jan@jantuomi.fi
+COMMENT= Telegram bot for household task management
+
+LICENSE= ISCL
+
+USE_GITHUB= yes
+GH_ACCOUNT= jantuomi
+GH_PROJECT= hommabot2
+GH_TAGNAME= main
+
+NO_CHECKSUM= yes
+NO_ARCH= yes
+NO_BUILD= yes
+
+RUN_DEPENDS= node:www/node \
+ npm:www/npm
+
+SUB_FILES= pkg-message
+USE_RC_SUBR= hommabot
+
+APPDIR= ${PREFIX}/hommabot
+STAGE_APPDIR= ${STAGEDIR}${APPDIR}
+
+do-install:
+ ${MKDIR} ${STAGE_APPDIR}
+ ${INSTALL_DATA} ${WRKSRC}/package.json ${STAGE_APPDIR}/
+ ${INSTALL_DATA} ${WRKSRC}/package-lock.json ${STAGE_APPDIR}/
+ ${INSTALL_DATA} ${WRKSRC}/tsconfig.json ${STAGE_APPDIR}/
+ (cd ${WRKSRC}/src && ${COPYTREE_SHARE} . ${STAGE_APPDIR}/src)
+
+post-install:
+ @(cd ${STAGEDIR}${PREFIX} && ${FIND} hommabot -type f -o -type l | ${SORT}) >> ${TMPPLIST}
+ @(cd ${STAGEDIR}${PREFIX} && ${FIND} hommabot -type d | ${SORT} -r | ${SED} 's|^|@dir |') >> ${TMPPLIST}
+
+.include <bsd.port.mk>
diff --git a/net-im/hommabot/files/hommabot.in b/net-im/hommabot/files/hommabot.in
new file mode 100644
index 0000000..df18e08
--- /dev/null
+++ b/net-im/hommabot/files/hommabot.in
@@ -0,0 +1,82 @@
+#!/bin/sh
+
+# PROVIDE: hommabot
+# REQUIRE: NETWORKING
+# KEYWORD: shutdown
+
+. /etc/rc.subr
+
+name="hommabot"
+rcvar="hommabot_enable"
+pidfile="/var/run/${name}.pid"
+
+load_rc_config $name
+
+: ${hommabot_enable:="NO"}
+: ${hommabot_token_file:="%%PREFIX%%/etc/hommabot.token"}
+: ${hommabot_data_dir:="/var/db/hommabot"}
+: ${hommabot_tz:="Europe/Helsinki"}
+
+start_cmd="${name}_start"
+stop_cmd="${name}_stop"
+status_cmd="${name}_status"
+
+hommabot_start()
+{
+ if [ ! -f "${hommabot_token_file}" ]; then
+ echo "${name}: token file ${hommabot_token_file} not found"
+ return 1
+ fi
+ echo "Starting ${name}..."
+ BOT_TOKEN=$(cat "${hommabot_token_file}")
+ if [ -z "${BOT_TOKEN}" ]; then
+ echo "${name}: token file ${hommabot_token_file} is empty"
+ return 1
+ fi
+ cd %%PREFIX%%/hommabot
+ if [ ! -d "%%PREFIX%%/hommabot/node_modules" ]; then
+ echo "${name}: installing dependencies (first run)..."
+ npm ci --prefix %%PREFIX%%/hommabot || {
+ echo "${name}: npm ci failed"
+ return 1
+ }
+ fi
+ mkdir -p "${hommabot_data_dir}"
+ chmod -R 770 "${hommabot_data_dir}"
+ daemon -p ${pidfile} -o /var/log/hommabot.log env \
+ PATH=%%PREFIX%%/bin:/usr/local/bin:/usr/bin:/bin \
+ TELEGRAM_BOT_TOKEN="${BOT_TOKEN}" \
+ SQLITE_PATH="${hommabot_data_dir}/db.sqlite3" \
+ TZ="${hommabot_tz}" \
+ npm start
+
+
+ for i in $(seq 5); do
+ sleep 1
+ if [ ! -f ${pidfile} ]; then
+ echo "Failed to start ${name}"
+ return 1
+ fi
+ done
+}
+
+hommabot_stop()
+{
+ if [ -f ${pidfile} ]; then
+ echo "Stopping ${name}..."
+ kill $(cat ${pidfile}) 2>/dev/null
+ rm -f ${pidfile}
+ fi
+}
+
+hommabot_status()
+{
+ if [ -f ${pidfile} ] && kill -0 $(cat ${pidfile}) 2>/dev/null; then
+ echo "${name} is running as pid $(cat ${pidfile})"
+ else
+ echo "${name} is not running"
+ return 1
+ fi
+}
+
+run_rc_command "$1"
diff --git a/net-im/hommabot/files/pkg-message.in b/net-im/hommabot/files/pkg-message.in
new file mode 100644
index 0000000..34b2602
--- /dev/null
+++ b/net-im/hommabot/files/pkg-message.in
@@ -0,0 +1,31 @@
+[
+ {
+ type: install
+ message: <<EOM
+========================================================================
+
+Hommabot requires a Telegram bot token.
+
+Create the token file:
+
+ install -m 600 /dev/null %%PREFIX%%/etc/hommabot.token
+
+Then edit it:
+
+ ee %%PREFIX%%/etc/hommabot.token
+
+Then enable the service and configure settings:
+
+ sysrc hommabot_enable=YES
+ sysrc hommabot_data_dir=/var/db/hommabot
+ sysrc hommabot_token_file=%%PREFIX%%/etc/hommabot.token
+ sysrc hommabot_tz=Europe/Helsinki
+
+Start the service:
+
+ service hommabot start
+
+========================================================================
+EOM
+ }
+]
diff --git a/net-im/hommabot/pkg-descr b/net-im/hommabot/pkg-descr
new file mode 100644
index 0000000..1ac6d9c
--- /dev/null
+++ b/net-im/hommabot/pkg-descr
@@ -0,0 +1 @@
+Telegram bot for household task management.
diff --git a/net-im/taulubot/Makefile b/net-im/taulubot/Makefile
new file mode 100644
index 0000000..7db66f7
--- /dev/null
+++ b/net-im/taulubot/Makefile
@@ -0,0 +1,50 @@
+PORTNAME= taulubot
+PORTVERSION= 1.0.0
+CATEGORIES= net-im
+
+MAINTAINER= jan@jantuomi.fi
+COMMENT= Telegram bot for funny pictures of paintings haha
+
+LICENSE= ISCL
+
+USE_GITHUB= yes
+GH_ACCOUNT= jantuomi
+GH_TAGNAME= v1.0.0
+
+NO_CHECKSUM= yes
+NO_ARCH= yes
+NO_BUILD= yes
+
+USES= python:3.11+
+RUN_DEPENDS= ${PYTHON_PKGNAMEPREFIX}aiohttp>0:www/py-aiohttp@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}pillow>0:graphics/py-pillow@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}python-dotenv>0:www/py-python-dotenv@${PY_FLAVOR}
+
+SUB_FILES= pkg-message
+SUB_LIST= PYTHON_CMD=${PYTHON_CMD}
+
+USE_RC_SUBR= taulubot
+
+APPDIR= ${PREFIX}/taulubot
+STAGE_APPDIR= ${STAGEDIR}${APPDIR}
+
+do-install:
+ ${MKDIR} ${STAGE_APPDIR}/app
+
+ ${INSTALL_DATA} ${WRKSRC}/taulubot.py ${STAGE_APPDIR}/app/
+ ${INSTALL_DATA} ${WRKSRC}/golden.png ${STAGE_APPDIR}/app/
+ ${INSTALL_DATA} ${WRKSRC}/wooden.png ${STAGE_APPDIR}/app/
+
+ (cd ${WRKSRC}/telepot && ${COPYTREE_SHARE} . ${STAGE_APPDIR}/app/telepot)
+
+post-install:
+ @(cd ${STAGEDIR}${PREFIX} && \
+ ${FIND} taulubot -type f -o -type l | \
+ ${SORT}) >> ${TMPPLIST}
+
+ @(cd ${STAGEDIR}${PREFIX} && \
+ ${FIND} taulubot -type d | \
+ ${SORT} -r | \
+ ${SED} 's|^|@dir |') >> ${TMPPLIST}
+
+.include <bsd.port.mk>
diff --git a/net-im/taulubot/distinfo b/net-im/taulubot/distinfo
new file mode 100644
index 0000000..62c07ce
--- /dev/null
+++ b/net-im/taulubot/distinfo
@@ -0,0 +1,3 @@
+TIMESTAMP = 1784702400
+SHA256 (jantuomi-taulubot-v1.0.0_GH0.tar.gz) = 6b7286a13ca0a78cdf58ae842f31315a3973d2d32b5f8065444f04c1daa8cf3d
+SIZE (jantuomi-taulubot-v1.0.0_GH0.tar.gz) = 1017042
diff --git a/net-im/taulubot/files/pkg-message.in b/net-im/taulubot/files/pkg-message.in
new file mode 100644
index 0000000..deeeac9
--- /dev/null
+++ b/net-im/taulubot/files/pkg-message.in
@@ -0,0 +1,30 @@
+[
+ {
+ type: install
+ message: <<EOM
+========================================================================
+
+Taulubot requires a Telegram bot token.
+
+Create the token file:
+
+ install -m 600 /dev/null %%PREFIX%%/etc/taulubot.token
+
+Then edit it:
+
+ ee %%PREFIX%%/etc/taulubot.token
+
+Then enable the service and configure settings:
+
+ sysrc taulubot_enable=YES
+ sysrc taulubot_photos_dir=/var/db/taulubot
+ sysrc taulubot_token_file=%%PREFIX%%/etc/taulubot.token
+
+Start the service:
+
+ service taulubot start
+
+========================================================================
+EOM
+ }
+]
diff --git a/net-im/taulubot/files/taulubot.in b/net-im/taulubot/files/taulubot.in
new file mode 100644
index 0000000..46a65be
--- /dev/null
+++ b/net-im/taulubot/files/taulubot.in
@@ -0,0 +1,81 @@
+#!/bin/sh
+
+# PROVIDE: taulubot
+# REQUIRE: NETWORKING
+# KEYWORD: shutdown
+
+. /etc/rc.subr
+
+name="taulubot"
+rcvar="taulubot_enable"
+pidfile="/var/run/${name}.pid"
+
+load_rc_config "$name"
+
+: ${taulubot_enable:="NO"}
+: ${taulubot_token_file:="%%PREFIX%%/etc/taulubot.token"}
+: ${taulubot_photos_dir:="/var/db/taulubot"}
+
+start_cmd="${name}_start"
+stop_cmd="${name}_stop"
+status_cmd="${name}_status"
+
+taulubot_start()
+{
+ if [ ! -f "${taulubot_token_file}" ]; then
+ echo "${name}: token file ${taulubot_token_file} not found"
+ return 1
+ fi
+
+ BOT_TOKEN=$(cat "${taulubot_token_file}")
+
+ if [ -z "${BOT_TOKEN}" ]; then
+ echo "${name}: token file ${taulubot_token_file} is empty"
+ return 1
+ fi
+
+ echo "Starting ${name}..."
+
+ cd %%PREFIX%%/taulubot/app || return 1
+
+ mkdir -p "${taulubot_photos_dir}"
+ chmod 770 "${taulubot_photos_dir}"
+
+ daemon -p "${pidfile}" -o /var/log/taulubot.log env \
+ PATH=%%PREFIX%%/bin:/usr/local/bin:/usr/bin:/bin \
+ PYTHONPATH=%%PREFIX%%/taulubot/vendor:%%PREFIX%%/taulubot/app \
+ BOT_TOKEN="${BOT_TOKEN}" \
+ PHOTOS_DIR="${taulubot_photos_dir}" \
+ %%PYTHON_CMD%% %%PREFIX%%/taulubot/app/taulubot.py
+
+ i=0
+ while [ "${i}" -lt 5 ]; do
+ sleep 1
+ if [ ! -f "${pidfile}" ]; then
+ echo "Failed to start ${name}"
+ return 1
+ fi
+ i=$((i + 1))
+ done
+}
+
+taulubot_stop()
+{
+ if [ -f "${pidfile}" ]; then
+ echo "Stopping ${name}..."
+ kill "$(cat "${pidfile}")" 2>/dev/null
+ rm -f "${pidfile}"
+ fi
+}
+
+taulubot_status()
+{
+ if [ -f "${pidfile}" ] && kill -0 "$(cat "${pidfile}")" 2>/dev/null; then
+ echo "${name} is running as pid $(cat "${pidfile}")"
+ else
+ echo "${name} is not running"
+ return 1
+ fi
+}
+
+run_rc_command "$1"
diff --git a/net-im/taulubot/pkg-descr b/net-im/taulubot/pkg-descr
new file mode 100644
index 0000000..0b9ad8b
--- /dev/null
+++ b/net-im/taulubot/pkg-descr
@@ -0,0 +1 @@
+Telegram bot for funny pictures of paintings haha
diff --git a/sysutils/pylogsentinel/Makefile b/sysutils/pylogsentinel/Makefile
new file mode 100644
index 0000000..1ec7960
--- /dev/null
+++ b/sysutils/pylogsentinel/Makefile
@@ -0,0 +1,19 @@
+PORTNAME= pylogsentinel
+PORTVERSION= 0.4.0
+CATEGORIES= sysutils
+MASTER_SITES= PYPI
+
+MAINTAINER= jan@jantuomi.fi
+COMMENT= Lightweight cron-friendly log monitoring utility
+
+LICENSE= MIT
+
+BUILD_DEPENDS= ${PYTHON_PKGNAMEPREFIX}setuptools>=61:devel/py-setuptools@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}wheel>0:devel/py-wheel@${PY_FLAVOR}
+
+USES= python:3.11+
+USE_PYTHON= pep517 autoplist
+
+NO_ARCH= yes
+
+.include <bsd.port.mk>
diff --git a/sysutils/pylogsentinel/distinfo b/sysutils/pylogsentinel/distinfo
new file mode 100644
index 0000000..c3dd8fd
--- /dev/null
+++ b/sysutils/pylogsentinel/distinfo
@@ -0,0 +1,3 @@
+TIMESTAMP = 1784702353
+SHA256 (pylogsentinel-0.4.0.tar.gz) = 69f3478fa95c795f0a34447f7083c6f8fdb7a37d80864ecb518a026d331b754b
+SIZE (pylogsentinel-0.4.0.tar.gz) = 18307
diff --git a/sysutils/pylogsentinel/pkg-descr b/sysutils/pylogsentinel/pkg-descr
new file mode 100644
index 0000000..dab5b1d
--- /dev/null
+++ b/sysutils/pylogsentinel/pkg-descr
@@ -0,0 +1,2 @@
+Lightweight cron-friendly log monitoring utility with regex rules
+and per-rule shell actions.
diff --git a/www/aggro/Makefile b/www/aggro/Makefile
new file mode 100644
index 0000000..7a69b4b
--- /dev/null
+++ b/www/aggro/Makefile
@@ -0,0 +1,61 @@
+PORTNAME= aggro
+PORTVERSION= 1.0.0
+CATEGORIES= www
+
+MAINTAINER= jan@jantuomi.fi
+COMMENT= Configurable web content aggregator with alerting
+
+LICENSE= MIT
+
+USE_GITHUB= yes
+GH_ACCOUNT= jantuomi
+GH_PROJECT= aggro
+GH_TAGNAME= main
+
+NO_CHECKSUM= yes
+NO_ARCH= yes
+NO_BUILD= yes
+
+USES= python:3.11+
+RUN_DEPENDS= ${PYTHON_PKGNAMEPREFIX}beautifulsoup>0:www/py-beautifulsoup@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}bottle>0:www/py-bottle@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}feedparser>0:textproc/py-feedparser@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}lxml>0:devel/py-lxml@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}python-dotenv>0:www/py-python-dotenv@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}requests>0:www/py-requests@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}schedule>0:devel/py-schedule@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}sgmllib3k>0:devel/py-sgmllib3k@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}tinydb>0:databases/py-tinydb@${PY_FLAVOR}
+
+SUB_LIST= PYTHON_CMD=${PYTHON_CMD}
+
+USE_RC_SUBR= aggro
+
+APPDIR= ${PREFIX}/aggro
+STAGE_APPDIR= ${STAGEDIR}${APPDIR}
+
+do-install:
+ ${MKDIR} ${STAGE_APPDIR}/app
+ ${MKDIR} ${STAGE_APPDIR}/plugins
+
+ ${INSTALL_DATA} ${WRKSRC}/aggro.py ${STAGE_APPDIR}/
+ (cd ${WRKSRC}/app && ${COPYTREE_SHARE} . ${STAGE_APPDIR}/app)
+ (cd ${WRKSRC}/plugins && ${COPYTREE_SHARE} . ${STAGE_APPDIR}/plugins)
+
+ ${INSTALL} -m 600 /dev/null ${STAGEDIR}${PREFIX}/etc/aggro.env
+ ${INSTALL_DATA} ${WRKSRC}/Aggrofile ${STAGEDIR}${PREFIX}/etc/Aggrofile.sample
+
+post-install:
+ @(cd ${STAGEDIR}${PREFIX} && \
+ ${FIND} aggro -type f -o -type l | \
+ ${SORT}) >> ${TMPPLIST}
+
+ @(cd ${STAGEDIR}${PREFIX} && \
+ ${FIND} aggro -type d | \
+ ${SORT} -r | \
+ ${SED} 's|^|@dir |') >> ${TMPPLIST}
+
+ @echo "etc/aggro.env" >> ${TMPPLIST}
+ @echo "etc/Aggrofile.sample" >> ${TMPPLIST}
+
+.include <bsd.port.mk>
diff --git a/www/aggro/files/aggro.in b/www/aggro/files/aggro.in
new file mode 100644
index 0000000..1e875d7
--- /dev/null
+++ b/www/aggro/files/aggro.in
@@ -0,0 +1,65 @@
+#!/bin/sh
+
+# PROVIDE: aggro
+# REQUIRE: NETWORKING
+# KEYWORD: shutdown
+
+. /etc/rc.subr
+
+name="aggro"
+rcvar="aggro_enable"
+pidfile="/var/run/${name}.pid"
+
+load_rc_config "$name"
+
+: ${aggro_enable:="NO"}
+: ${aggro_env_file:="%%PREFIX%%/etc/aggro.env"}
+: ${aggro_config:="%%PREFIX%%/etc/Aggrofile"}
+
+start_cmd="${name}_start"
+stop_cmd="${name}_stop"
+status_cmd="${name}_status"
+
+aggro_start()
+{
+ if [ ! -f "${aggro_env_file}" ]; then
+ echo "${name}: env file ${aggro_env_file} not found"
+ return 1
+ fi
+
+ if [ ! -f "${aggro_config}" ]; then
+ echo "${name}: config file ${aggro_config} not found"
+ return 1
+ fi
+
+ echo "Starting ${name}..."
+
+ cd %%PREFIX%%/aggro || return 1
+
+ daemon -p "${pidfile}" -o /var/log/aggro.log env \
+ PATH=%%PREFIX%%/bin:/usr/bin:/bin \
+ PYTHONPATH=%%PREFIX%%/aggro/vendor:%%PREFIX%%/aggro \
+ AGGRO_CONFIG_PATH="${aggro_config}" \
+ %%PYTHON_CMD%% %%PREFIX%%/aggro/aggro.py
+}
+
+aggro_stop()
+{
+ if [ -f "${pidfile}" ]; then
+ echo "Stopping ${name}..."
+ kill "$(cat "${pidfile}")" 2>/dev/null
+ rm -f "${pidfile}"
+ fi
+}
+
+aggro_status()
+{
+ if [ -f "${pidfile}" ] && kill -0 "$(cat "${pidfile}")" 2>/dev/null; then
+ echo "${name} is running as pid $(cat "${pidfile}")"
+ else
+ echo "${name} is not running"
+ return 1
+ fi
+}
+
+run_rc_command "$1"
diff --git a/www/aggro/pkg-descr b/www/aggro/pkg-descr
new file mode 100644
index 0000000..52089e3
--- /dev/null
+++ b/www/aggro/pkg-descr
@@ -0,0 +1,2 @@
+Configurable web content aggregator with plugin-based scrapers
+and alerting via SendGrid.
diff --git a/www/diddle/Makefile b/www/diddle/Makefile
new file mode 100644
index 0000000..3b26d1b
--- /dev/null
+++ b/www/diddle/Makefile
@@ -0,0 +1,58 @@
+PORTNAME= diddle
+PORTVERSION= 1.0.0
+CATEGORIES= www
+
+MAINTAINER= jan@jantuomi.fi
+COMMENT= Minimalist self-hosted Doodle alternative
+
+LICENSE= APACHE20
+
+USE_GITHUB= yes
+GH_ACCOUNT= jantuomi
+GH_PROJECT= diddle
+GH_TAGNAME= main
+
+NO_CHECKSUM= yes
+NO_ARCH= yes
+NO_BUILD= yes
+
+USES= python:3.11+
+RUN_DEPENDS= ${PYTHON_PKGNAMEPREFIX}Flask>0:www/py-flask@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}Flask-Compress>0:www/py-flask-compress@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}gunicorn>0:www/py-gunicorn@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}python-dotenv>0:www/py-python-dotenv@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}yaml>0:devel/py-pyyaml@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}ua-parser>0:www/py-ua-parser@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}user-agents>0:www/py-user-agents@${PY_FLAVOR}
+
+SUB_FILES= pkg-message
+SUB_LIST= PYTHON_CMD=${PYTHON_CMD}
+
+USE_RC_SUBR= diddle
+
+APPDIR= ${PREFIX}/diddle
+STAGE_APPDIR= ${STAGEDIR}${APPDIR}
+
+do-install:
+ ${MKDIR} ${STAGE_APPDIR}/app
+
+ ${INSTALL_DATA} ${WRKSRC}/app.py ${STAGE_APPDIR}/app/
+ ${INSTALL_DATA} ${WRKSRC}/db.py ${STAGE_APPDIR}/app/
+ ${INSTALL_DATA} ${WRKSRC}/email_client.py ${STAGE_APPDIR}/app/
+ ${INSTALL_DATA} ${WRKSRC}/apply_migrations.py ${STAGE_APPDIR}/app/
+
+ (cd ${WRKSRC}/migrations && ${COPYTREE_SHARE} . ${STAGE_APPDIR}/app/migrations)
+ (cd ${WRKSRC}/static && ${COPYTREE_SHARE} . ${STAGE_APPDIR}/app/static)
+ (cd ${WRKSRC}/templates && ${COPYTREE_SHARE} . ${STAGE_APPDIR}/app/templates)
+
+post-install:
+ @(cd ${STAGEDIR}${PREFIX} && \
+ ${FIND} diddle -type f -o -type l | \
+ ${SORT}) >> ${TMPPLIST}
+
+ @(cd ${STAGEDIR}${PREFIX} && \
+ ${FIND} diddle -type d | \
+ ${SORT} -r | \
+ ${SED} 's|^|@dir |') >> ${TMPPLIST}
+
+.include <bsd.port.mk>
diff --git a/www/diddle/files/diddle.in b/www/diddle/files/diddle.in
new file mode 100644
index 0000000..9efc9bf
--- /dev/null
+++ b/www/diddle/files/diddle.in
@@ -0,0 +1,71 @@
+#!/bin/sh
+
+# PROVIDE: diddle
+# REQUIRE: NETWORKING
+# KEYWORD: shutdown
+
+. /etc/rc.subr
+
+name="diddle"
+rcvar="diddle_enable"
+pidfile="/var/run/${name}.pid"
+
+load_rc_config "$name"
+
+: ${diddle_enable:="NO"}
+: ${diddle_env_file:="%%PREFIX%%/etc/diddle.env"}
+: ${diddle_bind:="0.0.0.0:8000"}
+: ${diddle_workers:="4"}
+
+start_cmd="${name}_start"
+stop_cmd="${name}_stop"
+status_cmd="${name}_status"
+
+diddle_start()
+{
+ if [ ! -f "${diddle_env_file}" ]; then
+ echo "${name}: env file ${diddle_env_file} not found"
+ return 1
+ fi
+
+ echo "Starting ${name}..."
+
+ cd %%PREFIX%%/diddle/app || return 1
+
+ # Run migrations
+ daemon -f env \
+ PYTHONPATH=%%PREFIX%%/diddle/vendor:%%PREFIX%%/diddle/app \
+ %%PYTHON_CMD%% %%PREFIX%%/diddle/app/apply_migrations.py
+
+ sleep 1
+
+ daemon -p "${pidfile}" -o /var/log/diddle.log env \
+ PATH=%%PREFIX%%/bin:/usr/local/bin:/usr/bin:/bin \
+ PYTHONPATH=%%PREFIX%%/diddle/vendor:%%PREFIX%%/diddle/app \
+ ENV_FILE="${diddle_env_file}" \
+ %%PREFIX%%/diddle/vendor/bin/gunicorn \
+ --bind "${diddle_bind}" \
+ -w "${diddle_workers}" \
+ app:app
+}
+
+diddle_stop()
+{
+ if [ -f "${pidfile}" ]; then
+ echo "Stopping ${name}..."
+ kill "$(cat "${pidfile}")" 2>/dev/null
+ rm -f "${pidfile}"
+ fi
+}
+
+diddle_status()
+{
+ if [ -f "${pidfile}" ] && kill -0 "$(cat "${pidfile}")" 2>/dev/null; then
+ echo "${name} is running as pid $(cat "${pidfile}")"
+ else
+ echo "${name} is not running"
+ return 1
+ fi
+}
+
+run_rc_command "$1"
diff --git a/www/diddle/files/pkg-message.in b/www/diddle/files/pkg-message.in
new file mode 100644
index 0000000..c3bac39
--- /dev/null
+++ b/www/diddle/files/pkg-message.in
@@ -0,0 +1,31 @@
+[
+ {
+ type: install
+ message: <<EOM
+========================================================================
+
+Diddle requires an environment file for configuration.
+
+Create the env file:
+
+ install -m 600 /dev/null %%PREFIX%%/etc/diddle.env
+
+Then edit it with your settings (DATABASE_URL, SECRET_KEY, etc.):
+
+ ee %%PREFIX%%/etc/diddle.env
+
+Then enable the service and configure settings:
+
+ sysrc diddle_enable=YES
+ sysrc diddle_env_file=%%PREFIX%%/etc/diddle.env
+ sysrc diddle_bind="0.0.0.0:8000"
+ sysrc diddle_workers=4
+
+Start the service:
+
+ service diddle start
+
+========================================================================
+EOM
+ }
+]
diff --git a/www/diddle/pkg-descr b/www/diddle/pkg-descr
new file mode 100644
index 0000000..ecd2685
--- /dev/null
+++ b/www/diddle/pkg-descr
@@ -0,0 +1 @@
+Minimalist scheduling. A mobile friendly, fast, self-hosted Doodle alternative.
diff --git a/www/expense-tracker/Makefile b/www/expense-tracker/Makefile
new file mode 100644
index 0000000..98532cd
--- /dev/null
+++ b/www/expense-tracker/Makefile
@@ -0,0 +1,36 @@
+PORTNAME= expense-tracker
+PORTVERSION= 1.0.1
+CATEGORIES= www
+
+MAINTAINER= jan@jantuomi.fi
+COMMENT= Minimal personal expense tracker
+
+LICENSE= AGPLv3
+
+USE_GITHUB= yes
+GH_ACCOUNT= jantuomi
+GH_PROJECT= expense-tracker
+GH_TAGNAME= main
+
+NO_CHECKSUM= yes
+NO_ARCH= yes
+NO_BUILD= yes
+
+USES= python:3.11+
+RUN_DEPENDS= ${PYTHON_CMD}:lang/python${PYTHON_SUFFIX}
+
+SUB_LIST= PYTHON_CMD=${PYTHON_CMD}
+
+USE_RC_SUBR= expense_tracker
+
+do-install:
+ ${MKDIR} ${STAGEDIR}${PREFIX}/expense-tracker
+ ${INSTALL_SCRIPT} ${WRKSRC}/app.py ${STAGEDIR}${PREFIX}/expense-tracker/
+ ${INSTALL_DATA} ${WRKSRC}/favicon.png ${STAGEDIR}${PREFIX}/expense-tracker/
+
+post-install:
+ @echo "expense-tracker/app.py" >> ${TMPPLIST}
+ @echo "expense-tracker/favicon.png" >> ${TMPPLIST}
+ @echo "@dir expense-tracker" >> ${TMPPLIST}
+
+.include <bsd.port.mk>
diff --git a/www/expense-tracker/files/expense_tracker.in b/www/expense-tracker/files/expense_tracker.in
new file mode 100644
index 0000000..b0ff573
--- /dev/null
+++ b/www/expense-tracker/files/expense_tracker.in
@@ -0,0 +1,56 @@
+#!/bin/sh
+
+# PROVIDE: expense_tracker
+# REQUIRE: NETWORKING
+# KEYWORD: shutdown
+
+. /etc/rc.subr
+
+name="expense_tracker"
+rcvar="expense_tracker_enable"
+pidfile="/var/run/${name}.pid"
+
+load_rc_config "$name"
+
+: ${expense_tracker_enable:="NO"}
+: ${expense_tracker_host:="0.0.0.0"}
+: ${expense_tracker_port:="80"}
+: ${expense_tracker_base_url:="http://localhost"}
+: ${expense_tracker_db:="/var/db/expense-tracker/expenses.db"}
+
+start_cmd="${name}_start"
+stop_cmd="${name}_stop"
+status_cmd="${name}_status"
+
+expense_tracker_start()
+{
+ echo "Starting ${name}..."
+ daemon -p "${pidfile}" -o /var/log/expense-tracker.log env \
+ PATH=%%PREFIX%%/bin:/usr/bin:/bin \
+ HOST="${expense_tracker_host}" \
+ PORT="${expense_tracker_port}" \
+ BASE_URL="${expense_tracker_base_url}" \
+ DB="${expense_tracker_db}" \
+ %%PYTHON_CMD%% %%PREFIX%%/expense-tracker/app.py
+}
+
+expense_tracker_stop()
+{
+ if [ -f "${pidfile}" ]; then
+ echo "Stopping ${name}..."
+ kill "$(cat "${pidfile}")" 2>/dev/null
+ rm -f "${pidfile}"
+ fi
+}
+
+expense_tracker_status()
+{
+ if [ -f "${pidfile}" ] && kill -0 "$(cat "${pidfile}")" 2>/dev/null; then
+ echo "${name} is running as pid $(cat "${pidfile}")"
+ else
+ echo "${name} is not running"
+ return 1
+ fi
+}
+
+run_rc_command "$1"
diff --git a/www/expense-tracker/pkg-descr b/www/expense-tracker/pkg-descr
new file mode 100644
index 0000000..f2acbe6
--- /dev/null
+++ b/www/expense-tracker/pkg-descr
@@ -0,0 +1,2 @@
+Minimal personal expense tracker with group support.
+Python stdlib only, no dependencies.
diff --git a/www/gdrive-photo-gallery/Makefile b/www/gdrive-photo-gallery/Makefile
new file mode 100644
index 0000000..aea1fd3
--- /dev/null
+++ b/www/gdrive-photo-gallery/Makefile
@@ -0,0 +1,44 @@
+PORTNAME= gdrive-photo-gallery
+PORTVERSION= 1.0.0
+CATEGORIES= www
+
+MAINTAINER= jan@jantuomi.fi
+COMMENT= Google Drive photo gallery web app
+
+LICENSE= MIT
+
+USE_GITHUB= yes
+GH_ACCOUNT= jantuomi
+GH_PROJECT= gdrive-photo-gallery
+GH_TAGNAME= d080e8a1e224
+
+NO_CHECKSUM= yes
+NO_ARCH= yes
+NO_BUILD= yes
+
+USES= python:3.12+
+RUN_DEPENDS= ${PYTHON_CMD}:lang/python${PYTHON_SUFFIX} \
+ uv:devel/uv
+
+SUB_LIST= PYTHON_CMD=${PYTHON_CMD}
+
+USE_RC_SUBR= gdrive_photo_gallery
+
+APPDIR= ${PREFIX}/gdrive-photo-gallery
+STAGE_APPDIR= ${STAGEDIR}${APPDIR}
+
+do-install:
+ ${MKDIR} ${STAGE_APPDIR}
+ (cd ${WRKSRC} && ${COPYTREE_SHARE} . ${STAGE_APPDIR})
+
+post-install:
+ @(cd ${STAGEDIR}${PREFIX} && \
+ ${FIND} gdrive-photo-gallery -type f -o -type l | \
+ ${SORT}) >> ${TMPPLIST}
+
+ @(cd ${STAGEDIR}${PREFIX} && \
+ ${FIND} gdrive-photo-gallery -type d | \
+ ${SORT} -r | \
+ ${SED} 's|^|@dir |') >> ${TMPPLIST}
+
+.include <bsd.port.mk>
diff --git a/www/gdrive-photo-gallery/files/gdrive_photo_gallery.in b/www/gdrive-photo-gallery/files/gdrive_photo_gallery.in
new file mode 100644
index 0000000..cf081f0
--- /dev/null
+++ b/www/gdrive-photo-gallery/files/gdrive_photo_gallery.in
@@ -0,0 +1,61 @@
+#!/bin/sh
+
+# PROVIDE: gdrive_photo_gallery
+# REQUIRE: NETWORKING
+# KEYWORD: shutdown
+
+. /etc/rc.subr
+
+name="gdrive_photo_gallery"
+rcvar="gdrive_photo_gallery_enable"
+pidfile="/var/run/${name}.pid"
+
+load_rc_config "$name"
+
+: ${gdrive_photo_gallery_enable:="NO"}
+: ${gdrive_photo_gallery_env_file:="%%PREFIX%%/etc/gdrive-photo-gallery.env"}
+: ${gdrive_photo_gallery_data_prefix:="/var/db/gdrive-photo-gallery"}
+
+start_cmd="${name}_start"
+stop_cmd="${name}_stop"
+status_cmd="${name}_status"
+
+gdrive_photo_gallery_start()
+{
+ if [ ! -f "${gdrive_photo_gallery_env_file}" ]; then
+ echo "${name}: env file ${gdrive_photo_gallery_env_file} not found"
+ return 1
+ fi
+
+ export PATH="$PATH:%%PREFIX%%/bin"
+ echo "Starting ${name}..."
+
+ . "${gdrive_photo_gallery_env_file}"
+ export GDRIVE_FOLDER GAPI_KEY AUTHOR IG_LINK BASE_URL DATA_PREFIX
+ export DATA_PREFIX="${gdrive_photo_gallery_data_prefix}"
+
+ daemon -p "${pidfile}" -o /var/log/gdrive-photo-gallery.log \
+ %%PREFIX%%/bin/uv run --directory %%PREFIX%%/gdrive-photo-gallery \
+ gunicorn --bind 0.0.0.0:80 app:app -w 1
+}
+
+gdrive_photo_gallery_stop()
+{
+ if [ -f "${pidfile}" ]; then
+ echo "Stopping ${name}..."
+ kill "$(cat "${pidfile}")" 2>/dev/null
+ rm -f "${pidfile}"
+ fi
+}
+
+gdrive_photo_gallery_status()
+{
+ if [ -f "${pidfile}" ] && kill -0 "$(cat "${pidfile}")" 2>/dev/null; then
+ echo "${name} is running as pid $(cat "${pidfile}")"
+ else
+ echo "${name} is not running"
+ return 1
+ fi
+}
+
+run_rc_command "$1"
diff --git a/www/gdrive-photo-gallery/pkg-descr b/www/gdrive-photo-gallery/pkg-descr
new file mode 100644
index 0000000..0522e09
--- /dev/null
+++ b/www/gdrive-photo-gallery/pkg-descr
@@ -0,0 +1,2 @@
+Google Drive photo gallery web application.
+Displays photos from a Google Drive folder as a gallery.
diff --git a/www/pastebin/Makefile b/www/pastebin/Makefile
new file mode 100644
index 0000000..6c18ca7
--- /dev/null
+++ b/www/pastebin/Makefile
@@ -0,0 +1,35 @@
+PORTNAME= pastebin
+PORTVERSION= 2.0.2
+CATEGORIES= www
+
+MAINTAINER= jan@jantuomi.fi
+COMMENT= Minimal pastebin service
+WWW= https://github.com/w4/bin
+
+LICENSE= MIT
+
+LIB_DEPENDS= libgit2.so:devel/libgit2 \
+ libonig.so:devel/oniguruma
+
+USES= cargo localbase
+
+USE_GITHUB= yes
+GH_ACCOUNT= w4
+GH_PROJECT= bin
+GH_TAGNAME= v2.0.2
+
+NO_CHECKSUM= yes
+
+CARGO_INSTALL= no
+
+PLIST_FILES= bin/pastebin
+
+SUB_LIST= PREFIX=${PREFIX}
+USE_RC_SUBR= pastebin
+
+.sinclude "${.CURDIR}/Makefile.crates"
+
+do-install:
+ ${INSTALL_PROGRAM} ${CARGO_TARGET_DIR}/release/bin ${STAGEDIR}${PREFIX}/bin/pastebin
+
+.include <bsd.port.mk>
diff --git a/www/pastebin/Makefile.crates b/www/pastebin/Makefile.crates
new file mode 100644
index 0000000..9ba4be8
--- /dev/null
+++ b/www/pastebin/Makefile.crates
@@ -0,0 +1,305 @@
+CARGO_CRATES= actix-0.13.5 \
+ actix-codec-0.5.2 \
+ actix-http-3.11.0 \
+ actix-macros-0.2.4 \
+ actix-router-0.5.3 \
+ actix-rt-2.10.0 \
+ actix-server-2.6.0 \
+ actix-service-2.0.3 \
+ actix-utils-3.0.1 \
+ actix-web-4.11.0 \
+ actix-web-codegen-4.3.0 \
+ actix_derive-0.6.2 \
+ addr2line-0.24.2 \
+ adler2-2.0.0 \
+ aho-corasick-1.1.3 \
+ alloc-no-stdlib-2.0.4 \
+ alloc-stdlib-0.2.2 \
+ ansi_colours-1.2.3 \
+ anstream-0.6.18 \
+ anstyle-1.0.10 \
+ anstyle-parse-0.2.6 \
+ anstyle-query-1.1.2 \
+ anstyle-wincon-3.0.8 \
+ anyhow-1.0.98 \
+ argh-0.1.13 \
+ argh_derive-0.1.13 \
+ argh_shared-0.1.13 \
+ askama-0.14.0 \
+ askama_derive-0.14.0 \
+ askama_parser-0.14.0 \
+ autocfg-1.4.0 \
+ backtrace-0.3.75 \
+ base64-0.22.1 \
+ basic-toml-0.1.10 \
+ bat-0.25.0 \
+ bincode-1.3.3 \
+ bitflags-1.3.2 \
+ bitflags-2.9.1 \
+ block-buffer-0.10.4 \
+ brotli-8.0.1 \
+ brotli-decompressor-5.0.0 \
+ bstr-1.12.0 \
+ bugreport-0.5.1 \
+ bytemuck-1.23.0 \
+ bytes-1.10.1 \
+ bytesize-1.3.3 \
+ bytestring-1.4.0 \
+ cc-1.2.24 \
+ cfg-if-1.0.0 \
+ clap-4.5.38 \
+ clap_builder-4.5.38 \
+ clap_lex-0.7.4 \
+ clircle-0.6.1 \
+ cloudabi-0.0.3 \
+ colorchoice-1.0.3 \
+ console-0.15.11 \
+ content_inspector-0.2.4 \
+ cookie-0.16.2 \
+ core-foundation-sys-0.8.7 \
+ cpufeatures-0.2.17 \
+ crc32fast-1.4.2 \
+ crossbeam-channel-0.5.15 \
+ crossbeam-deque-0.8.6 \
+ crossbeam-epoch-0.9.18 \
+ crossbeam-utils-0.8.21 \
+ crypto-common-0.1.6 \
+ darling-0.20.11 \
+ darling_core-0.20.11 \
+ darling_macro-0.20.11 \
+ deranged-0.4.0 \
+ derive_more-2.0.1 \
+ derive_more-impl-2.0.1 \
+ digest-0.10.7 \
+ displaydoc-0.2.5 \
+ either-1.15.0 \
+ encode_unicode-1.0.0 \
+ encoding_rs-0.8.35 \
+ env_logger-0.10.2 \
+ equivalent-1.0.2 \
+ errno-0.3.12 \
+ etcetera-0.8.0 \
+ flate2-1.1.1 \
+ fnv-1.0.7 \
+ foldhash-0.1.5 \
+ form_urlencoded-1.2.1 \
+ fuchsia-cprng-0.1.1 \
+ futures-0.3.31 \
+ futures-channel-0.3.31 \
+ futures-core-0.3.31 \
+ futures-executor-0.3.31 \
+ futures-io-0.3.31 \
+ futures-macro-0.3.31 \
+ futures-sink-0.3.31 \
+ futures-task-0.3.31 \
+ futures-util-0.3.31 \
+ generic-array-0.14.7 \
+ getrandom-0.3.3 \
+ gimli-0.31.1 \
+ git-version-0.3.9 \
+ git-version-macro-0.3.9 \
+ git2-0.19.0 \
+ glob-0.3.2 \
+ globset-0.4.16 \
+ gpw-0.1.0 \
+ grep-cli-0.1.11 \
+ h2-0.3.26 \
+ hashbrown-0.15.3 \
+ hermit-abi-0.5.1 \
+ home-0.5.11 \
+ htmlescape-0.3.1 \
+ http-0.2.12 \
+ httparse-1.10.1 \
+ httpdate-1.0.3 \
+ humantime-2.2.0 \
+ icu_collections-2.0.0 \
+ icu_locale_core-2.0.0 \
+ icu_normalizer-2.0.0 \
+ icu_normalizer_data-2.0.0 \
+ icu_properties-2.0.1 \
+ icu_properties_data-2.0.1 \
+ icu_provider-2.0.0 \
+ ident_case-1.0.1 \
+ idna-1.0.3 \
+ idna_adapter-1.2.1 \
+ impl-more-0.1.9 \
+ indexmap-2.9.0 \
+ is-terminal-0.4.16 \
+ is_terminal_polyfill-1.70.1 \
+ itertools-0.7.11 \
+ itertools-0.13.0 \
+ itoa-1.0.15 \
+ jobserver-0.1.33 \
+ language-tags-0.3.2 \
+ libc-0.2.172 \
+ libgit2-sys-0.17.0+1.8.1 \
+ libz-sys-1.1.22 \
+ linked-hash-map-0.5.6 \
+ linux-raw-sys-0.9.4 \
+ litemap-0.8.0 \
+ local-channel-0.1.5 \
+ local-waker-0.1.4 \
+ lock_api-0.4.12 \
+ log-0.4.27 \
+ matrixmultiply-0.1.15 \
+ memchr-2.7.4 \
+ mime-0.3.17 \
+ miniz_oxide-0.8.8 \
+ mio-1.0.4 \
+ ndarray-0.12.1 \
+ ntapi-0.4.1 \
+ nu-ansi-term-0.50.1 \
+ num-complex-0.2.4 \
+ num-conv-0.1.0 \
+ num-traits-0.2.19 \
+ object-0.36.7 \
+ once_cell-1.21.3 \
+ once_cell_polyfill-1.70.1 \
+ onig-6.5.1 \
+ onig_sys-69.9.1 \
+ parking_lot-0.12.3 \
+ parking_lot_core-0.9.10 \
+ path_abs-0.5.1 \
+ percent-encoding-2.3.1 \
+ pin-project-lite-0.2.16 \
+ pin-utils-0.1.0 \
+ pkg-config-0.3.32 \
+ plist-1.7.1 \
+ potential_utf-0.1.2 \
+ powerfmt-0.2.0 \
+ ppv-lite86-0.2.21 \
+ pretty_env_logger-0.5.0 \
+ proc-macro2-1.0.95 \
+ quick-xml-0.32.0 \
+ quote-1.0.40 \
+ r-efi-5.2.0 \
+ rand-0.5.6 \
+ rand-0.9.1 \
+ rand_chacha-0.9.0 \
+ rand_core-0.3.1 \
+ rand_core-0.4.2 \
+ rand_core-0.9.3 \
+ rawpointer-0.1.0 \
+ rayon-1.10.0 \
+ rayon-core-1.12.1 \
+ redox_syscall-0.5.12 \
+ regex-1.11.1 \
+ regex-automata-0.4.9 \
+ regex-lite-0.1.6 \
+ regex-syntax-0.8.5 \
+ rgb-0.8.50 \
+ rust-fuzzy-search-0.1.1 \
+ rustc-demangle-0.1.24 \
+ rustc-hash-2.1.1 \
+ rustix-1.0.7 \
+ ryu-1.0.20 \
+ same-file-1.0.6 \
+ scopeguard-1.2.0 \
+ semver-1.0.26 \
+ serde-1.0.219 \
+ serde_derive-1.0.219 \
+ serde_json-1.0.140 \
+ serde_spanned-0.6.8 \
+ serde_urlencoded-0.7.1 \
+ serde_with-3.12.0 \
+ serde_with_macros-3.12.0 \
+ serde_yaml-0.9.34+deprecated \
+ sha1-0.10.6 \
+ shell-escape-0.1.5 \
+ shell-words-1.1.0 \
+ shlex-1.3.0 \
+ signal-hook-registry-1.4.5 \
+ slab-0.4.9 \
+ smallvec-1.15.0 \
+ socket2-0.5.9 \
+ stable_deref_trait-1.2.0 \
+ std_prelude-0.2.12 \
+ strsim-0.11.1 \
+ syn-2.0.101 \
+ synstructure-0.13.2 \
+ syntect-5.2.0 \
+ sysinfo-0.33.1 \
+ termcolor-1.4.1 \
+ terminal-colorsaurus-0.4.8 \
+ terminal-trx-0.2.4 \
+ terminal_size-0.4.2 \
+ thiserror-1.0.69 \
+ thiserror-impl-1.0.69 \
+ time-0.3.41 \
+ time-core-0.1.4 \
+ time-macros-0.2.22 \
+ tinystr-0.8.1 \
+ tokio-1.45.1 \
+ tokio-util-0.7.15 \
+ toml-0.8.22 \
+ toml_datetime-0.6.9 \
+ toml_edit-0.22.26 \
+ toml_write-0.1.1 \
+ tracing-0.1.41 \
+ tracing-attributes-0.1.28 \
+ tracing-core-0.1.33 \
+ typenum-1.18.0 \
+ unicode-ident-1.0.18 \
+ unicode-width-0.1.14 \
+ unicode-width-0.2.0 \
+ unicode-xid-0.2.6 \
+ unsafe-libyaml-0.2.11 \
+ url-2.5.4 \
+ utf8_iter-1.0.4 \
+ utf8parse-0.2.2 \
+ vcpkg-0.2.15 \
+ version_check-0.9.5 \
+ walkdir-2.5.0 \
+ wasi-0.11.0+wasi-snapshot-preview1 \
+ wasi-0.14.2+wasi-0.2.4 \
+ wild-2.2.1 \
+ winapi-0.3.9 \
+ winapi-i686-pc-windows-gnu-0.4.0 \
+ winapi-util-0.1.9 \
+ winapi-x86_64-pc-windows-gnu-0.4.0 \
+ windows-0.56.0 \
+ windows-0.57.0 \
+ windows-core-0.56.0 \
+ windows-core-0.57.0 \
+ windows-implement-0.56.0 \
+ windows-implement-0.57.0 \
+ windows-interface-0.56.0 \
+ windows-interface-0.57.0 \
+ windows-result-0.1.2 \
+ windows-sys-0.48.0 \
+ windows-sys-0.52.0 \
+ windows-sys-0.59.0 \
+ windows-targets-0.48.5 \
+ windows-targets-0.52.6 \
+ windows_aarch64_gnullvm-0.48.5 \
+ windows_aarch64_gnullvm-0.52.6 \
+ windows_aarch64_msvc-0.48.5 \
+ windows_aarch64_msvc-0.52.6 \
+ windows_i686_gnu-0.48.5 \
+ windows_i686_gnu-0.52.6 \
+ windows_i686_gnullvm-0.52.6 \
+ windows_i686_msvc-0.48.5 \
+ windows_i686_msvc-0.52.6 \
+ windows_x86_64_gnu-0.48.5 \
+ windows_x86_64_gnu-0.52.6 \
+ windows_x86_64_gnullvm-0.48.5 \
+ windows_x86_64_gnullvm-0.52.6 \
+ windows_x86_64_msvc-0.48.5 \
+ windows_x86_64_msvc-0.52.6 \
+ winnow-0.7.10 \
+ wit-bindgen-rt-0.39.0 \
+ writeable-0.6.1 \
+ xterm-color-1.0.1 \
+ yaml-rust-0.4.5 \
+ yoke-0.8.0 \
+ yoke-derive-0.8.0 \
+ zerocopy-0.8.25 \
+ zerocopy-derive-0.8.25 \
+ zerofrom-0.1.6 \
+ zerofrom-derive-0.1.6 \
+ zerotrie-0.2.2 \
+ zerovec-0.11.2 \
+ zerovec-derive-0.11.1 \
+ zstd-0.13.3 \
+ zstd-safe-7.2.4 \
+ zstd-sys-2.0.15+zstd.1.5.7
diff --git a/www/pastebin/distinfo b/www/pastebin/distinfo
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/www/pastebin/distinfo
diff --git a/www/pastebin/files/pastebin.in b/www/pastebin/files/pastebin.in
new file mode 100644
index 0000000..bf499bf
--- /dev/null
+++ b/www/pastebin/files/pastebin.in
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+# PROVIDE: pastebin
+# REQUIRE: NETWORKING
+# KEYWORD: shutdown
+
+. /etc/rc.subr
+
+name="pastebin"
+rcvar="pastebin_enable"
+pidfile="/var/run/${name}.pid"
+
+load_rc_config "$name"
+
+: ${pastebin_enable:="NO"}
+: ${pastebin_listen:="0.0.0.0:80"}
+
+start_cmd="${name}_start"
+stop_cmd="${name}_stop"
+status_cmd="${name}_status"
+
+pastebin_start()
+{
+ echo "Starting ${name}..."
+ daemon -p "${pidfile}" -o /var/log/pastebin.log \
+ %%PREFIX%%/bin/pastebin "${pastebin_listen}"
+}
+
+pastebin_stop()
+{
+ if [ -f "${pidfile}" ]; then
+ echo "Stopping ${name}..."
+ kill "$(cat "${pidfile}")" 2>/dev/null
+ rm -f "${pidfile}"
+ fi
+}
+
+pastebin_status()
+{
+ if [ -f "${pidfile}" ] && kill -0 "$(cat "${pidfile}")" 2>/dev/null; then
+ echo "${name} is running as pid $(cat "${pidfile}")"
+ else
+ echo "${name} is not running"
+ return 1
+ fi
+}
+
+run_rc_command "$1"
diff --git a/www/pastebin/pkg-descr b/www/pastebin/pkg-descr
new file mode 100644
index 0000000..bbb5bad
--- /dev/null
+++ b/www/pastebin/pkg-descr
@@ -0,0 +1 @@
+A minimal pastebin service written in Rust.
diff --git a/www/py-ua-parser/Makefile b/www/py-ua-parser/Makefile
new file mode 100644
index 0000000..afb4b30
--- /dev/null
+++ b/www/py-ua-parser/Makefile
@@ -0,0 +1,20 @@
+PORTNAME= ua-parser
+PORTVERSION= 0.18.0
+CATEGORIES= www python
+MASTER_SITES= PYPI
+PKGNAMEPREFIX= ${PYTHON_PKGNAMEPREFIX}
+
+MAINTAINER= jan@jantuomi.fi
+COMMENT= Python port of Browserscope's user agent parser
+WWW= https://github.com/ua-parser/uap-python
+
+LICENSE= APACHE20
+
+BUILD_DEPENDS= ${PYTHON_PKGNAMEPREFIX}setuptools>0:devel/py-setuptools@${PY_FLAVOR}
+
+USES= python:3.11+
+USE_PYTHON= distutils autoplist
+
+NO_ARCH= yes
+
+.include <bsd.port.mk>
diff --git a/www/py-ua-parser/distinfo b/www/py-ua-parser/distinfo
new file mode 100644
index 0000000..8a4e598
--- /dev/null
+++ b/www/py-ua-parser/distinfo
@@ -0,0 +1,3 @@
+TIMESTAMP = 1784811424
+SHA256 (ua-parser-0.18.0.tar.gz) = db51f1b59bfaa82ed9e2a1d99a54d3e4153dddf99ac1435d51828165422e624e
+SIZE (ua-parser-0.18.0.tar.gz) = 40659
diff --git a/www/py-ua-parser/pkg-descr b/www/py-ua-parser/pkg-descr
new file mode 100644
index 0000000..8df7d72
--- /dev/null
+++ b/www/py-ua-parser/pkg-descr
@@ -0,0 +1,2 @@
+Python port of Browserscope's user agent parser. This library parses
+HTTP User-Agent strings into their component parts (browser, OS, device).
diff --git a/www/py-user-agents/Makefile b/www/py-user-agents/Makefile
new file mode 100644
index 0000000..6b6f588
--- /dev/null
+++ b/www/py-user-agents/Makefile
@@ -0,0 +1,21 @@
+PORTNAME= user-agents
+PORTVERSION= 2.2.0
+CATEGORIES= www python
+MASTER_SITES= PYPI
+PKGNAMEPREFIX= ${PYTHON_PKGNAMEPREFIX}
+
+MAINTAINER= jan@jantuomi.fi
+COMMENT= Library to identify devices by parsing user agent strings
+WWW= https://github.com/selwin/python-user-agents
+
+LICENSE= MIT
+
+BUILD_DEPENDS= ${PYTHON_PKGNAMEPREFIX}setuptools>0:devel/py-setuptools@${PY_FLAVOR}
+RUN_DEPENDS= ${PYTHON_PKGNAMEPREFIX}ua-parser>=0.10.0:www/py-ua-parser@${PY_FLAVOR}
+
+USES= python:3.11+
+USE_PYTHON= distutils autoplist
+
+NO_ARCH= yes
+
+.include <bsd.port.mk>
diff --git a/www/py-user-agents/distinfo b/www/py-user-agents/distinfo
new file mode 100644
index 0000000..ccd92bf
--- /dev/null
+++ b/www/py-user-agents/distinfo
@@ -0,0 +1,3 @@
+TIMESTAMP = 1784811453
+SHA256 (user-agents-2.2.0.tar.gz) = d36d25178db65308d1458c5fa4ab39c9b2619377010130329f3955e7626ead26
+SIZE (user-agents-2.2.0.tar.gz) = 9525
diff --git a/www/py-user-agents/pkg-descr b/www/py-user-agents/pkg-descr
new file mode 100644
index 0000000..edfdf9b
--- /dev/null
+++ b/www/py-user-agents/pkg-descr
@@ -0,0 +1,2 @@
+A library to identify devices (phones, tablets) and their capabilities
+by parsing browser user agent strings.