Help packaging suitenumerique/meet as a FreeBSD port (npm + Django + dependencies)

Hello,
I'm a new intern at No Parking and I'm currently working on my first FreeBSD-related project.
The goal is to package Visio (the video conferencing application from the French government's La Suite collaborative platform) as a FreeBSD port. I've already managed to install it manually, but I'm now learning how to turn it into a proper port and build it with poudriere.
I'm new to the FreeBSD ports ecosystem and would appreciate any advice on the best way to proceed.

The project involves four different components: Keycloak (which already exists as a port, net/keycloak), LiveKit, Meet, and Apache. At the moment, I'm having the most difficulties with the Meet package, especially regarding its dependencies and the overall porting process.

Makefile:
PORTNAME=       meet
DISTVERSIONPREFIX=    v
DISTVERSION=    1.17.0
CATEGORIES=     www net

MAINTAINER=     helene@noparking.net
COMMENT=        La Suite Numérique -> Meet
WWW=            https://github.com/suitenumerique/meet

LICENSE=        MIT

USE_RC_SUBR=    meet
USES=           python:3.11+ nodejs:24
USE_PYTHON=     flavors

BUILD_DEPENDS=  ${LOCALBASE}/bin/npm:www/npm-node24

RUN_DEPENDS=    gunicorn:www/py-gunicorn \
        postgresql16-client:databases/postgresql16-client \
        livekit-server:net/livekit \
        redis-cli:databases/redis    \
        ${PYTHON_PKGNAMEPREFIX}django52>=0:www/py-django52 \
        ${PYTHON_PKGNAMEPREFIX}gunicorn>=0:www/py-gunicorn \
        ${PYTHON_PKGNAMEPREFIX}redis>=0:databases/py-redis \
        ${PYTHON_PKGNAMEPREFIX}dj-database-url>=0:www/py-dj-database-url \
        ${PYTHON_PKGNAMEPREFIX}django-configurations>=0:www/py-django-configurations \
        ${PYTHON_PKGNAMEPREFIX}django-cors-headers>=0:www/py-django-cors-headers \
        ${PYTHON_PKGNAMEPREFIX}django-countries>=0:www/py-django-countries \
        ${PYTHON_PKGNAMEPREFIX}django-filter>=0:www/py-django-filter \
        ${PYTHON_PKGNAMEPREFIX}django-redis>=0:www/py-django-redis \
        ${PYTHON_PKGNAMEPREFIX}django-storages>=0:www/py-django-storages \
        ${PYTHON_PKGNAMEPREFIX}django-timezone-field>=0:www/py-django-timezone-field \
        ${PYTHON_PKGNAMEPREFIX}django-pydantic-field>=0:www/py-django-pydantic-field \
        ${PYTHON_PKGNAMEPREFIX}djangorestframework>=0:www/py-djangorestframework \
        ${PYTHON_PKGNAMEPREFIX}drf-spectacular>=0:www/py-drf-spectacular \
        ${PYTHON_PKGNAMEPREFIX}factory-boy>=0:devel/py-factory-boy \
        ${PYTHON_PKGNAMEPREFIX}jsonschema>=0:devel/py-jsonschema \
        ${PYTHON_PKGNAMEPREFIX}markdown>=0:textproc/py-markdown \
        ${PYTHON_PKGNAMEPREFIX}PyJWT>=0:security/py-pyjwt \
        ${PYTHON_PKGNAMEPREFIX}python-magic>=0:devel/py-magic \
        ${PYTHON_PKGNAMEPREFIX}requests>=0:www/py-requests \
        ${PYTHON_PKGNAMEPREFIX}sentry-sdk>=0:devel/py-sentry-sdk \
        ${PYTHON_PKGNAMEPREFIX}whitenoise>=0:www/py-whitenoise \
        ${PYTHON_PKGNAMEPREFIX}mozilla-django-oidc>=0:www/py-mozilla-django-oidc \
        ${PYTHON_PKGNAMEPREFIX}aiohttp>=0:www/py-aiohttp \
        ${PYTHON_PKGNAMEPREFIX}urllib3>=0:www/py-urllib3 \
        ${PYTHON_PKGNAMEPREFIX}boto3>=0:devel/py-boto3 \
        ${PYTHON_PKGNAMEPREFIX}python-frontmatter>=0:py-python-frontmatter \

USE_GITHUB=     yes
GH_ACCOUNT=     suitenumerique
GH_PROJECT=     meet

PREFIX?=        /usr/local
MEET_DIR=       ${PREFIX}/meet
PLIST_SUB=      MEET_DIR=${MEET_DIR}
SUB_LIST=       MEET_DIR=${MEET_DIR} \
                PREFIX=${PREFIX}
SUB_FILES=      pkg-message

USERS=          meet
GROUPS=         meet

do-build:
    @${ECHO_MSG} "===> Build du frontend Meet..."
    cd ${WRKSRC}/src/frontend && \
        HOME=${WRKDIR} \
        npm_config_cache=${WRKDIR}/.npm-cache \
        ${LOCALBASE}/bin/npm install --no-package-lock  --verbose
    @${ECHO_MSG} "===> npm install terminé, lancement du build..."
    cd ${WRKSRC}/src/frontend && \
        HOME=${WRKDIR} \
        ${LOCALBASE}/bin/npm run build
    @${ECHO_MSG} "===> Vérification du dist..."
    ls ${WRKSRC}/src/frontend/dist

do-install:
    ${MKDIR} ${STAGEDIR}${MEET_DIR}
    ${MKDIR} ${STAGEDIR}${MEET_DIR}/src
    ${MKDIR} ${STAGEDIR}/var/log/meet
    ${MKDIR} ${STAGEDIR}/var/run/meet

    cd ${WRKSRC}/src/backend && \
        ${COPYTREE_SHARE} . ${STAGEDIR}${MEET_DIR}/src/backend

    cd ${WRKSRC}/src/frontend/dist && \
        ${COPYTREE_SHARE} . ${STAGEDIR}${MEET_DIR}/src/frontend/dist

    ${INSTALL_DATA} ${FILESDIR}/meet.env.sample \
        ${STAGEDIR}${MEET_DIR}/src/backend/.env.sample

    ${MKDIR} ${STAGEDIR}${PREFIX}/etc/rc.d
    ${INSTALL_SCRIPT} ${FILESDIR}/meet_backend.rc \
        ${STAGEDIR}${PREFIX}/etc/rc.d/meet_backend

post-install:
    cd ${STAGEDIR} && \
        find .${MEET_DIR} -type f | \
        sed 's|^\./usr/local/||' | sort >> ${TMPPLIST}
    echo "@owner meet" >> ${TMPPLIST}
    echo "@group meet" >> ${TMPPLIST}
    echo "@dir /var/log/meet" >> ${TMPPLIST}
    echo "@dir /var/run/meet" >> ${TMPPLIST}
    echo "@owner root" >> ${TMPPLIST}
    echo "@group wheel" >> ${TMPPLIST}

.include <bsd.port.mk>

Below are the steps I performed manually. There may be mistakes, and I may have omitted some details that I forgot to document:

Markdown (GitHub flavored):
## Installer les dependances
sudo pkg update
sudo pkg install python311 py311-pip py311-virtualenv py311-gunicorn node npm git go gmake pkgconf apache24 postgresql16-server postgresql16-client  redis

## Activer les services
### Postgresql
sysrc postgresql_enable="YES"
service postgresql initdb
service postgresql start
### Redis
sysrc redis_enable="YES"
service redis start

# Créer la base et l'utilisateur
psql -U postgres <<EOF
CREATE USER meet WITH PASSWORD 'toor';
CREATE DATABASE meet OWNER meet;
EOF

## Compiler LiveKit
git clone https://github.com/livekit/livekit /opt/livekit
cd /opt/livekit
go build -o /usr/local/bin/livekit-server ./cmd/server  -> si a marche checker binaire : ls -lh /usr/local/bin/livekit-server && livekit-server --version

touch /usr/local/etc/livekit.yaml
```
port: 7880
rtc:
  tcp_port: 7881
  udp_port: 7882
  use_external_ip: true
redis:
  address: 127.0.0.1:6379
keys:
  monApiKey: secret
turn:
  enabled: true
  domain: 10.0.0.218
  tls_port: 5349
```

Pour tester : livekit-server --config /usr/local/etc/livekit.yaml --dev

## Keycloak (OIDC — obligatoire) -> Meet utilise OpenID Connect pour l'authentification
Keycloak -> Java
pkg install openjdk21
fetch https://github.com/keycloak/keycloak/releases/download/26.0.0/keycloak-26.0.0.tar.gz
tar xf keycloak-26.0.0.tar.gz -C /opt/
cd /opt/keycloak-26.0.0
bin/kc.sh start-dev --http-port=8080 &          # mod dev

pour tester sur une autre machine
export KC_BOOTSTRAP_ADMIN_USERNAME=admin
export KC_BOOTSTRAP_ADMIN_PASSWORD=admin

Et dans Keycloak (http://10.0.0.218:8080) :
Créer un realm meet
Créer un client meet-client (type: confidential, OIDC)
Noter le client_id et le client_secret
Créer un utilisateur test

name: meet client
client_id: 12

## Backend
git clone https://github.com/suitenumerique/meet /opt/meet
cd /opt/meet/src/backend

### Virtualenv Python
python3.11 -m venv venv
. venv/bin/activate
pip install -r requirements.txt

fichier d'env:
touch /opt/meet/src/backend/.env
```sh
# Django
DJANGO_SECRET_KEY=une_cle_tres_longue_et_aleatoire_50_chars
DJANGO_ALLOWED_HOSTS=meet.noparking.tld,10.0.0.218
DJANGO_SETTINGS_MODULE=meet.settings

# Base de données
DB_HOST=localhost
DB_NAME=meet
DB_USER=dinum
DB_PASSWORD=toor
DB_PORT=5432

# Redis
REDIS_URL=redis://127.0.0.1:6379/0 (change to valkey)

# LiveKit
LIVEKIT_API_KEY=monApiKey
LIVEKIT_API_SECRET=secret
LIVEKIT_API_URL=ws://10.0.0.218

# OIDC (Keycloak)
OIDC_RP_CLIENT_ID=helene
OIDC_RP_CLIENT_SECRET=5Oi8Sp95Mi1FPXd6ZIlLkuddKQnzNeTz
OIDC_OP_AUTHORIZATION_ENDPOINT=https://10.0.0.218:8443/realms/meet/protocol/openid-connect/auth
OIDC_OP_TOKEN_ENDPOINT=https://10.0.0.218:8443/realms/meet/protocol/openid-connect/token
OIDC_OP_USER_ENDPOINT=https://10.0.0.218:8443/realms/meet/protocol/openid-connect/userinfo
OIDC_OP_JWKS_ENDPOINT=https://10.0.0.218:8443/realms/meet/protocol/openid-connect/certs
```

# Migrations et superuser

echo 'DJANGO_CONFIGURATION=Development' >> /opt/meet/src/backend/.env


python manage.py migrate / venv/bin/python -m configurations.management migrate
python manage.py createsuperuser --email helene@noparking.net / venv/bin/python -m configurations.management createsuperuser --email helene@noparking.net


export DJANGO_SETTINGS_MODULE=meet.settings_dev

pour tester : python manage.py runserver 0.0.0.0:8000
venv/bin/python -v -m configurations.management runserver 0.0.0.0:8000

venv/bin/gunicorn meet.wsgi:application \
  --bind 0.0.0.0:8000 \
  --worker-tmp-dir /tmp \
  --workers 2

set -a && . .env && set +a && export DJANGO_CONFIGURATION=Development && export DJANGO_SETTINGS_MODULE=meet.settings && venv/bin/python -m configurations.management migrate --verbosity=3 2>&1

run le front
cd /opt/meet/src/frontend
npm install
npm run build

# Apache

dans /usr/local/etc/apache24/httpd.conf décommenter :

LoadModule proxy_module libexec/apache24/mod_proxy.so
LoadModule proxy_http_module libexec/apache24/mod_proxy_http.so
LoadModule proxy_wstunnel_module libexec/apache24/mod_proxy_wstunnel.so
LoadModule ssl_module libexec/apache24/mod_ssl.so
LoadModule rewrite_module libexec/apache24/mod_rewrite.so
LoadModule headers_module libexec/apache24/mod_headers.so
LoadModule ssl_module libexec/apache24/mod_ssl.so

Include etc/apache24/extra/httpd-vhosts.conf
Include etc/apache24/extra/httpd-ssl.conf

sudo apachectl configtest && sudo service apache24 restart

touch sites/meet.conf
```
<VirtualHost *:80>
    ServerName 10.0.0.218

    DocumentRoot /opt/meet/src/frontend/dist

    <Directory /opt/meet/src/frontend/dist>
        Options -Indexes
        AllowOverride None
        Require all granted
 
        FallbackResource /index.html
    </Directory>

    ProxyPreserveHost On

    ProxyPass /api/ http://127.0.0.1:8000/api/
    ProxyPassReverse /api/ http://127.0.0.1:8000/api/

    ProxyPass /admin/ http://127.0.0.1:8000/admin/
    ProxyPassReverse /admin/ http://127.0.0.1:8000/admin/

    ProxyPass /jwks http://127.0.0.1:8000/jwks
    ProxyPassReverse /jwks http://127.0.0.1:8000/jwks

    RewriteEngine On
    RewriteCond %{HTTP:Upgrade} websocket [NC]
    RewriteRule /livekit/(.*) ws://127.0.0.1:7880/$1 [P,L]
    ProxyPass /livekit/ http://127.0.0.1:7880/
    ProxyPassReverse /livekit/ http://127.0.0.1:7880/
</VirtualHost>

<VirtualHost *:443>
    ServerName 10.0.0.218

    SSLEngine On
    SSLCertificateFile    /usr/local/etc/ssl/meet.crt
    SSLCertificateKeyFile /usr/local/etc/ssl/meet.key

    DocumentRoot /opt/meet/src/frontend/dist

    <Directory /opt/meet/src/frontend/dist>
        Options -Indexes
        AllowOverride None
        Require all granted
        FallbackResource /index.html
    </Directory>

    ProxyPreserveHost On
    RequestHeader set X-Forwarded-Proto "https"

    ProxyPass /api/ http://127.0.0.1:8000/api/
    ProxyPassReverse /api/ http://127.0.0.1:8000/api/

    ProxyPass /admin/ http://127.0.0.1:8000/admin/
    ProxyPassReverse /admin/ http://127.0.0.1:8000/admin/

    ProxyPass /jwks http://127.0.0.1:8000/jwks
    ProxyPassReverse /jwks http://127.0.0.1:8000/jwks

    RewriteEngine On
    RewriteCond %{HTTP:Upgrade} websocket [NC]
    RewriteRule /livekit/(.*) ws://127.0.0.1:7880/$1 [P,L]
    ProxyPass /livekit/ http://127.0.0.1:7880/
    ProxyPassReverse /livekit/ http://127.0.0.1:7880/
</VirtualHost>
```

Since this is also my first time posting on the FreeBSD forums, please let me know if I should provide more details or if there is a better place to ask this kind of question.
Thank you for your time and any guidance you can provide.
 
It's a golang application? Yet no go crates are being pulled in? Poudriere won't allow fetching of additional files during the build phase.

Code:
## Compiler LiveKit
git clone https://github.com/livekit/livekit /opt/livekit
cd /opt/livekit
go build -o /usr/local/bin/livekit-server ./cmd/server  -> si a marche checker binaire : ls -lh /usr/local/bin/livekit-server && livekit-server --version

touch /usr/local/etc/livekit.yaml
If this 'meet' application depends on 'LiveKit', then you need to build a port for Livekit first. Every dependency will need its own port. You cannot depend on something that isn't ported.
 
Back
Top