This commit is contained in:
Alan Martines 2026-05-22 10:30:30 -03:00 committed by GitHub
commit 6d524aa57c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 887 additions and 0 deletions

24
net/cloudflared/LICENSE Normal file
View file

@ -0,0 +1,24 @@
BSD 2-Clause License
Copyright (c) 2026, Alan Martines
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.
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.

8
net/cloudflared/Makefile Normal file
View file

@ -0,0 +1,8 @@
PLUGIN_NAME= cloudflared
PLUGIN_VERSION= 0.1.0
PLUGIN_REVISION= 1
PLUGIN_COMMENT= Cloudflare Tunnel (cloudflared) integration
PLUGIN_MAINTAINER= alancpmartines@hotmail.com
PLUGIN_DEPENDS= fetch
.include "../../Mk/plugins.mk"

17
net/cloudflared/pkg-descr Normal file
View file

@ -0,0 +1,17 @@
Cloudflare Tunnel (cloudflared) integration for OPNsense.
Provides a native MVC interface to manage token-based Cloudflare Zero
Trust tunnels without opening firewall ports or requiring a static IP.
Follows Method 1: Token-based Setup using binaries from the kjake
FreeBSD fork of cloudflared.
Features:
- Token-based tunnel authentication via Cloudflare Zero Trust
- Integrated binary installer with automatic FreeBSD version and
architecture detection
- QUIC kernel tuning (kern.ipc.maxsockbuf, net.inet.udp.recvspace)
- Post-quantum encryption support (--post-quantum)
- Real-time tunnel health status in the UI
- Appears in System: Diagnostics: Services
WWW: https://github.com/AlanMartines/os-cloudflared

View file

@ -0,0 +1,53 @@
<?php
/*
* Copyright (C) 2026 Alan Martines <alancpmartines@hotmail.com>
* All rights reserved.
*
* 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.
*
* THIS SOFTWARE IS PROVIDED ``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
* AUTHOR 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.
*/
function cloudflared_enabled()
{
$model = new \OPNsense\Cloudflared\Cloudflared();
return (string)$model->general->enabled == '1';
}
function cloudflared_services()
{
$services = [];
if (cloudflared_enabled()) {
$services[] = [
'description' => gettext('Cloudflare Tunnel'),
'configd' => [
'restart' => ['cloudflared restart'],
'start' => ['cloudflared start'],
'stop' => ['cloudflared stop'],
],
'name' => 'cloudflared',
'pidfile' => '/var/run/cloudflared.pid',
];
}
return $services;
}

View file

@ -0,0 +1,56 @@
<?php
namespace OPNsense\Cloudflared\Api;
use OPNsense\Base\ApiMutableServiceControllerBase;
use OPNsense\Core\Backend;
class ServiceController extends ApiMutableServiceControllerBase
{
protected static $internalServiceClass = 'OPNsense\Cloudflared\Cloudflared';
protected static $internalServiceEnabled = 'general.enabled';
protected static $internalServiceTemplate = 'OPNsense/Cloudflared';
protected static $internalServiceName = 'cloudflared';
/**
* Reconfigura o serviço: cria diretórios, recarrega templates,
* aplica sysctl tunables e reinicia o serviço.
*/
public function reconfigureAction()
{
if ($this->request->isPost()) {
$backend = new Backend();
$backend->configdRun("cloudflared reconfigure");
return ['status' => 'ok'];
}
return ['status' => 'failed'];
}
public function tunnelStatusAction()
{
$backend = new Backend();
$response = $backend->configdRun("cloudflared tunnel_status");
$data = json_decode(trim($response), true);
if ($data === null) {
return ['tunnel' => 'unknown'];
}
return $data;
}
public function installAction()
{
if ($this->request->isPost()) {
$backend = new Backend();
$response = $backend->configdRun("cloudflared install_binary");
if ($response === null) {
return ['response' => 'ERROR: configd did not respond. Run "service configd restart" on OPNsense.'];
}
$response = trim($response);
if ($response === '' || $response === 'FAILED') {
return ['response' => 'ERROR: Action not found. Run "service configd restart" on OPNsense to reload actions.'];
}
return ['response' => $response];
}
return ['response' => 'error'];
}
}

View file

@ -0,0 +1,12 @@
<?php
namespace OPNsense\Cloudflared\Api;
use OPNsense\Base\ApiMutableModelControllerBase;
use OPNsense\Cloudflared\Cloudflared;
class SettingsController extends ApiMutableModelControllerBase
{
protected static $internalModelName = 'Cloudflared';
protected static $internalModelClass = 'OPNsense\Cloudflared\Cloudflared';
}

View file

@ -0,0 +1,40 @@
<?php
/**
* Copyright (C) 2026 Alan Martines <alancpmartines@hotmail.com>
* All rights reserved.
*
* 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.
*
* THIS SOFTWARE IS PROVIDED ``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
* AUTHOR 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.
*/
namespace OPNsense\Cloudflared;
use OPNsense\Base\IndexController as BaseIndexController;
class IndexController extends BaseIndexController
{
public function indexAction()
{
$this->view->generalForm = $this->getForm("general");
$this->view->pick('OPNsense/Cloudflared/index');
}
}

View file

@ -0,0 +1,38 @@
<form>
<field>
<id>Cloudflared.general.enabled</id>
<label>Enable</label>
<type>checkbox</type>
<help>Enable Cloudflare Tunnel</help>
</field>
<field>
<id>Cloudflared.general.token</id>
<label>Tunnel Token</label>
<type>password</type>
<help>The token for your Cloudflare Tunnel. Get it from one.dash.cloudflare.com > Access > Tunnels.</help>
</field>
<field>
<id>Cloudflared.general.no_autoupdate</id>
<label>Disable Auto-Update</label>
<type>checkbox</type>
<help>Pass --no-autoupdate to cloudflared. Recommended when managing updates manually via the Install/Update Binary button.</help>
</field>
<field>
<id>Cloudflared.general.post_quantum</id>
<label>Enable Post-Quantum Encryption</label>
<type>checkbox</type>
<help>Pass --post-quantum to enable post-quantum cryptography for the tunnel connection.</help>
</field>
<field>
<id>Cloudflared.general.kern_ipc_maxsockbuf</id>
<label>Max Socket Buffer (kern.ipc.maxsockbuf)</label>
<type>text</type>
<help>Recommended value for QUIC performance. Default is 16777216.</help>
</field>
<field>
<id>Cloudflared.general.net_inet_udp_recvspace</id>
<label>UDP Recv Space (net.inet.udp.recvspace)</label>
<type>text</type>
<help>Recommended value for QUIC performance. Default is 8388608.</help>
</field>
</form>

View file

@ -0,0 +1,93 @@
msgid ""
msgstr ""
"Project-Id-Version: os-cloudflared\n"
"Language: en_US\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
# Menu & ACL
msgid "Cloudflare Tunnel"
msgstr "Cloudflare Tunnel"
msgid "Settings"
msgstr "Settings"
msgid "Allow access to Cloudflare Tunnel settings"
msgstr "Allow access to Cloudflare Tunnel settings"
msgid "Allow access to Cloudflare Tunnel service status/control"
msgstr "Allow access to Cloudflare Tunnel service status/control"
# General Form
msgid "General Settings"
msgstr "General Settings"
msgid "Enable"
msgstr "Enable"
msgid "Tunnel Token"
msgstr "Tunnel Token"
msgid "Disable Auto-Update"
msgstr "Disable Auto-Update"
msgid "Enable Post-Quantum Encryption"
msgstr "Enable Post-Quantum Encryption"
msgid "Max Socket Buffer (kern.ipc.maxsockbuf)"
msgstr "Max Socket Buffer (kern.ipc.maxsockbuf)"
msgid "UDP Recv Space (net.inet.udp.recvspace)"
msgstr "UDP Recv Space (net.inet.udp.recvspace)"
msgid "Enable Cloudflare Tunnel"
msgstr "Enable Cloudflare Tunnel"
msgid "The token for your Cloudflare Tunnel. Get it from one.dash.cloudflare.com > Access > Tunnels."
msgstr "The token for your Cloudflare Tunnel. Get it from one.dash.cloudflare.com > Access > Tunnels."
msgid "Pass --no-autoupdate to cloudflared. Recommended when managing updates manually via the Install/Update Binary button."
msgstr "Pass --no-autoupdate to cloudflared. Recommended when managing updates manually via the Install/Update Binary button."
msgid "Pass --post-quantum to enable post-quantum cryptography for the tunnel connection."
msgstr "Pass --post-quantum to enable post-quantum cryptography for the tunnel connection."
msgid "Recommended value for QUIC performance. Default is 16777216."
msgstr "Recommended value for QUIC performance. Default is 16777216."
msgid "Recommended value for QUIC performance. Default is 8388608."
msgstr "Recommended value for QUIC performance. Default is 8388608."
# UI Buttons
msgid "Apply"
msgstr "Apply"
msgid "Install/Update Binary"
msgstr "Install/Update Binary"
msgid "Start"
msgstr "Start"
msgid "Stop"
msgstr "Stop"
msgid "Restart"
msgstr "Restart"
msgid "Running"
msgstr "Running"
msgid "Stopped"
msgstr "Stopped"
msgid "No response from server. Check if configd is running."
msgstr "No response from server. Check if configd is running."
msgid "Tunnel"
msgstr "Tunnel"
msgid "Healthy"
msgstr "Healthy"
msgid "Connecting"
msgstr "Connecting"

View file

@ -0,0 +1,93 @@
msgid ""
msgstr ""
"Project-Id-Version: os-cloudflared\n"
"Language: pt_BR\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
# Menu & ACL
msgid "Cloudflare Tunnel"
msgstr "Cloudflare Tunnel"
msgid "Settings"
msgstr "Configurações"
msgid "Allow access to Cloudflare Tunnel settings"
msgstr "Permitir acesso às configurações do Cloudflare Tunnel"
msgid "Allow access to Cloudflare Tunnel service status/control"
msgstr "Permitir acesso ao status/controle do serviço Cloudflare Tunnel"
# General Form
msgid "General Settings"
msgstr "Configurações Gerais"
msgid "Enable"
msgstr "Habilitar"
msgid "Tunnel Token"
msgstr "Token do Túnel"
msgid "Disable Auto-Update"
msgstr "Desativar Atualização Automática"
msgid "Enable Post-Quantum Encryption"
msgstr "Habilitar Criptografia Pós-Quântica"
msgid "Max Socket Buffer (kern.ipc.maxsockbuf)"
msgstr "Buffer Máximo de Socket (kern.ipc.maxsockbuf)"
msgid "UDP Recv Space (net.inet.udp.recvspace)"
msgstr "Espaço de Recebimento UDP (net.inet.udp.recvspace)"
msgid "Enable Cloudflare Tunnel"
msgstr "Habilitar Cloudflare Tunnel"
msgid "The token for your Cloudflare Tunnel. Get it from one.dash.cloudflare.com > Access > Tunnels."
msgstr "O token do seu Cloudflare Tunnel. Obtenha em one.dash.cloudflare.com > Access > Tunnels."
msgid "Pass --no-autoupdate to cloudflared. Recommended when managing updates manually via the Install/Update Binary button."
msgstr "Passa --no-autoupdate ao cloudflared. Recomendado quando as atualizações são gerenciadas manualmente pelo botão Instalar/Atualizar Binário."
msgid "Pass --post-quantum to enable post-quantum cryptography for the tunnel connection."
msgstr "Passa --post-quantum para habilitar criptografia pós-quântica na conexão do túnel."
msgid "Recommended value for QUIC performance. Default is 16777216."
msgstr "Valor recomendado para desempenho do QUIC. O padrão é 16777216."
msgid "Recommended value for QUIC performance. Default is 8388608."
msgstr "Valor recomendado para desempenho do QUIC. O padrão é 8388608."
# UI Buttons
msgid "Apply"
msgstr "Aplicar"
msgid "Install/Update Binary"
msgstr "Instalar/Atualizar Binário"
msgid "Start"
msgstr "Iniciar"
msgid "Stop"
msgstr "Parar"
msgid "Restart"
msgstr "Reiniciar"
msgid "Running"
msgstr "Executando"
msgid "Stopped"
msgstr "Parado"
msgid "No response from server. Check if configd is running."
msgstr "Sem resposta do servidor. Verifique se o configd está em execução."
msgid "Tunnel"
msgstr "Túnel"
msgid "Healthy"
msgstr "Saudável"
msgid "Connecting"
msgstr "Conectando"

View file

@ -0,0 +1,9 @@
<acl>
<page-services-cloudflared>
<name>Services: Cloudflare Tunnel</name>
<patterns>
<pattern>ui/cloudflared/*</pattern>
<pattern>api/cloudflared/*</pattern>
</patterns>
</page-services-cloudflared>
</acl>

View file

@ -0,0 +1,9 @@
<?php
namespace OPNsense\Cloudflared;
use OPNsense\Base\BaseModel;
class Cloudflared extends BaseModel
{
}

View file

@ -0,0 +1,27 @@
<model>
<mount>//OPNsense/Cloudflared</mount>
<description>Cloudflare Tunnel</description>
<items>
<general>
<enabled type="BooleanField">
<default>0</default>
</enabled>
<token type="TextField">
<Required>Y</Required>
<maskMatch>/^[a-zA-Z0-9\._\-]+$/</maskMatch>
</token>
<no_autoupdate type="BooleanField">
<default>1</default>
</no_autoupdate>
<post_quantum type="BooleanField">
<default>0</default>
</post_quantum>
<kern_ipc_maxsockbuf type="IntegerField">
<default>16777216</default>
</kern_ipc_maxsockbuf>
<net_inet_udp_recvspace type="IntegerField">
<default>8388608</default>
</net_inet_udp_recvspace>
</general>
</items>
</model>

View file

@ -0,0 +1,5 @@
<menu>
<Services>
<Cloudflared order="490" VisibleName="Cloudflare Tunnel" cssClass="fa fa-cloud fa-fw" url="/ui/cloudflared/"/>
</Services>
</menu>

View file

@ -0,0 +1,178 @@
{#
# Copyright (C) 2026 Alan Martines <alancpmartines@hotmail.com>
# All rights reserved.
#}
<script>
$(document).ready(function() {
var data_get_map = {'frm_general': "/api/cloudflared/settings/get"};
mapDataToFormUI(data_get_map).done(function(data) {
$('.selectpicker').selectpicker('refresh');
// Adiciona botão de mostrar/ocultar ao campo token
var tokenInput = $("#Cloudflared\\.general\\.token");
tokenInput.wrap('<div class="input-group"></div>');
tokenInput.after(
'<span class="input-group-btn">' +
'<button class="btn btn-default" type="button" id="toggleTokenBtn" title="{{ lang._('Show/Hide') }}">' +
'<i class="fa fa-eye" id="toggleTokenIcon"></i>' +
'</button>' +
'</span>'
);
$("#toggleTokenBtn").click(function() {
var inp = $("#Cloudflared\\.general\\.token");
var icon = $("#toggleTokenIcon");
if (inp.attr("type") === "password") {
inp.attr("type", "text");
icon.removeClass("fa-eye").addClass("fa-eye-slash");
} else {
inp.attr("type", "password");
icon.removeClass("fa-eye-slash").addClass("fa-eye");
}
});
});
var i18n = {
running: "{{ lang._('Running') }}",
stopped: "{{ lang._('Stopped') }}",
healthy: "{{ lang._('Healthy') }}",
connecting: "{{ lang._('Connecting') }}",
tunnel_status: "{{ lang._('Tunnel') }}"
};
// Salvar e reconfigura o serviço
$("#saveAct").click(function() {
$("#saveAct_progress").addClass("fa fa-spinner fa-pulse");
saveFormToEndpoint("/api/cloudflared/settings/set", 'frm_general', function() {
ajaxCall("/api/cloudflared/service/reconfigure", {}, function() {
updateServiceStatus(i18n);
$("#saveAct_progress").removeClass("fa fa-spinner fa-pulse").addClass("fa fa-check text-success");
setTimeout(function() {
$("#saveAct_progress").removeClass("fa fa-check text-success");
}, 2000);
});
});
});
// Instalar / atualizar binário
$("#installBtn").click(function() {
$("#installBtn").prop('disabled', true);
$("#installIcon").addClass('fa-spinner fa-spin').removeClass('fa-download');
ajaxCall("/api/cloudflared/service/install", {}, function(data, status) {
$("#installBtn").prop('disabled', false);
$("#installIcon").addClass('fa-download').removeClass('fa-spinner fa-spin');
var output = (data && data.response) ? data.response.trim() : '';
var dlgType = (output.indexOf("successful") !== -1)
? BootstrapDialog.TYPE_SUCCESS
: BootstrapDialog.TYPE_DANGER;
BootstrapDialog.show({
type: dlgType,
title: "{{ lang._('Install/Update Binary') }}",
message: $('<pre/>').text(output || "{{ lang._('No response from server. Check if configd is running.') }}"),
buttons: [{ label: 'OK', action: function(d) { d.close(); } }]
});
});
});
// Controle de serviço
$("#startBtn").click(function() {
ajaxCall("/api/cloudflared/service/start", {}, function() { updateServiceStatus(i18n); });
});
$("#stopBtn").click(function() {
ajaxCall("/api/cloudflared/service/stop", {}, function() { updateServiceStatus(i18n); });
});
$("#restartBtn").click(function() {
ajaxCall("/api/cloudflared/service/restart", {}, function() { updateServiceStatus(i18n); });
});
updateServiceStatus(i18n);
});
function updateServiceStatus(i18n) {
ajaxCall("/api/cloudflared/service/status", {}, function(data) {
var running = data && data.status === "running";
if (running) {
$("#svc_status").html(
'<span class="label label-success">'
+ '<i class="fa fa-play fa-fw"></i> ' + i18n.running
+ '</span>'
);
$("#startBtn").hide();
$("#stopBtn, #restartBtn").show();
updateTunnelStatus(i18n);
} else {
$("#svc_status").html(
'<span class="label label-danger">'
+ '<i class="fa fa-stop fa-fw"></i> ' + i18n.stopped
+ '</span>'
);
$("#tunnel_health").html('');
$("#startBtn").show();
$("#stopBtn, #restartBtn").hide();
}
});
}
function updateTunnelStatus(i18n) {
ajaxCall("/api/cloudflared/service/tunnel_status", {}, function(data) {
var state = data && data.tunnel ? data.tunnel : 'unknown';
var html = '';
if (state === 'healthy') {
html = '&nbsp;<span class="label label-success">'
+ '<i class="fa fa-cloud fa-fw"></i> ' + i18n.tunnel_status + ': ' + i18n.healthy
+ '</span>';
} else if (state === 'connecting') {
html = '&nbsp;<span class="label label-warning">'
+ '<i class="fa fa-cloud fa-fw"></i> ' + i18n.tunnel_status + ': ' + i18n.connecting
+ '</span>';
}
$("#tunnel_health").html(html);
});
}
</script>
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
<li class="active"><a data-toggle="tab" href="#general">{{ lang._('General Settings') }}</a></li>
</ul>
<div class="tab-content content-box">
<div id="general" class="tab-pane fade in active">
<div class="content-box-main">
{{ partial("layout_partials/base_form",['fields':generalForm,'id':'frm_general'])}}
<div class="col-md-12" style="padding-bottom: 15px;">
<hr />
<button class="btn btn-primary" id="saveAct" type="button">
<b>{{ lang._('Apply') }}</b> <i id="saveAct_progress"></i>
</button>
<button class="btn btn-default" id="installBtn" type="button">
<i id="installIcon" class="fa fa-download fa-fw"></i>
<b>{{ lang._('Install/Update Binary') }}</b>
</button>
</div>
</div>
</div>
</div>
<div class="content-box" style="padding: 8px 15px; margin-top: 1em;">
<table style="width: 100%">
<tr>
<td>
<b>{{ lang._('Cloudflare Tunnel') }}</b>
&nbsp;
<span id="svc_status"><i class="fa fa-spinner fa-spin"></i></span>
<span id="tunnel_health"></span>
</td>
<td style="text-align: right">
<button class="btn btn-xs btn-default" id="startBtn" type="button" style="display:none;">
<i class="fa fa-play fa-fw"></i> {{ lang._('Start') }}
</button>
<button class="btn btn-xs btn-default" id="stopBtn" type="button" style="display:none;">
<i class="fa fa-stop fa-fw"></i> {{ lang._('Stop') }}
</button>
<button class="btn btn-xs btn-default" id="restartBtn" type="button" style="display:none;">
<i class="fa fa-repeat fa-fw"></i> {{ lang._('Restart') }}
</button>
</td>
</tr>
</table>
</div>

View file

@ -0,0 +1,51 @@
#!/bin/sh
log_msg() {
logger -t cloudflared-install "$1"
echo "$1"
}
DEST="/usr/local/bin/cloudflared"
GITHUB_API="https://api.github.com/repos/kjake/cloudflared/releases/latest"
API_CACHE="/tmp/cloudflared_release.json"
OS_VERSION=$(uname -r | cut -d'.' -f1)
ARCH=$(uname -m)
log_msg "Detected FreeBSD ${OS_VERSION} / ${ARCH}"
# Fetch latest release metadata
log_msg "Fetching latest release version from GitHub..."
fetch -o "${API_CACHE}" "${GITHUB_API}" > /dev/null 2>&1
if [ ! -s "${API_CACHE}" ]; then
log_msg "ERROR: Could not reach GitHub API. Check internet connectivity."
exit 1
fi
# Parse tag_name using Python (reliable on single-line JSON)
LATEST_TAG=$(python3 -c "import json,sys; print(json.load(open('${API_CACHE}'))['tag_name'])" 2>/dev/null)
if [ -z "${LATEST_TAG}" ]; then
log_msg "ERROR: Could not parse release version from GitHub API response."
exit 1
fi
rm -f "${API_CACHE}"
log_msg "Latest version: ${LATEST_TAG}"
BINARY_NAME="cloudflared-freebsd${OS_VERSION}-${ARCH}"
BINARY_URL="https://github.com/kjake/cloudflared/releases/download/${LATEST_TAG}/${BINARY_NAME}"
log_msg "Downloading ${BINARY_NAME}..."
fetch -o "${DEST}" "${BINARY_URL}" > /dev/null 2>&1
if [ $? -eq 0 ] && [ -s "${DEST}" ]; then
chmod +x "${DEST}"
log_msg "Installation of cloudflared ${LATEST_TAG} successful."
else
rm -f "${DEST}"
log_msg "ERROR: Download failed. Binary may not exist for FreeBSD ${OS_VERSION} / ${ARCH}."
log_msg "URL attempted: ${BINARY_URL}"
exit 1
fi

View file

@ -0,0 +1,59 @@
#!/bin/sh
log_msg() {
logger -t cloudflared-reconfigure "$1"
echo "$1"
}
log_msg "Starting cloudflared reconfiguration..."
# Dados estáticos: criar diretórios apenas se não existirem
if [ ! -d /usr/local/etc/cloudflared ]; then
mkdir -p /usr/local/etc/cloudflared
chmod 750 /usr/local/etc/cloudflared
log_msg "Created /usr/local/etc/cloudflared"
fi
if [ ! -d /usr/local/etc/sysctl.conf.d ]; then
mkdir -p /usr/local/etc/sysctl.conf.d
log_msg "Created /usr/local/etc/sysctl.conf.d"
fi
# Dados dinâmicos: recarregar templates (rc.conf.d e token sempre atualizados)
log_msg "Reloading configuration templates..."
/usr/local/sbin/configctl template reload OPNsense/Cloudflared
# Proteger token: chmod 600 (nunca deve ser world-readable)
if [ -f /usr/local/etc/cloudflared/token ]; then
chmod 600 /usr/local/etc/cloudflared/token
log_msg "Token file permissions set to 600."
fi
# Apply sysctl tunables se existirem
if [ -f /usr/local/etc/sysctl.conf.d/cloudflared.conf ]; then
log_msg "Applying sysctl tunables..."
while IFS= read -r line; do
case "$line" in
\#*|'') continue ;;
esac
key=$(echo "$line" | cut -d'=' -f1)
val=$(echo "$line" | cut -d'=' -f2-)
if sysctl -w "${key}=${val}" > /dev/null 2>&1; then
log_msg "sysctl ${key}=${val} applied."
else
log_msg "WARNING: sysctl ${key}=${val} failed (may require reboot)."
fi
done < /usr/local/etc/sysctl.conf.d/cloudflared.conf
fi
# Verificar se o binário existe antes de tentar iniciar
if [ ! -x /usr/local/bin/cloudflared ]; then
log_msg "WARNING: /usr/local/bin/cloudflared not found. Use 'Install/Update Binary' first."
exit 0
fi
# Reiniciar serviço
log_msg "Restarting cloudflared service..."
service cloudflared restart
log_msg "Reconfiguration complete."

View file

@ -0,0 +1,23 @@
#!/bin/sh
# Verifica se o processo está rodando via PID file
if [ ! -f /var/run/cloudflared.pid ]; then
echo '{"tunnel":"stopped"}'
exit 0
fi
PID=$(cat /var/run/cloudflared.pid 2>/dev/null)
if [ -z "${PID}" ] || ! kill -0 "${PID}" 2>/dev/null; then
echo '{"tunnel":"stopped"}'
exit 0
fi
# Consulta o health check local do cloudflared (padrão: localhost:2000)
# HTTP 200 = tunnel conectado ao Cloudflare
# HTTP 503 / falha = processo rodando mas ainda conectando
fetch -T 3 -qo /dev/null "http://localhost:2000/healthcheck" 2>/dev/null
if [ $? -eq 0 ]; then
echo '{"tunnel":"healthy"}'
else
echo '{"tunnel":"connecting"}'
fi

View file

@ -0,0 +1,41 @@
[start]
command:/usr/local/etc/rc.d/cloudflared start
parameters:
type:script
message:Starting Cloudflare Tunnel
[stop]
command:/usr/local/etc/rc.d/cloudflared stop
parameters:
type:script
message:Stopping Cloudflare Tunnel
[restart]
command:/usr/local/etc/rc.d/cloudflared restart
parameters:
type:script
message:Restarting Cloudflare Tunnel
[status]
command:/usr/local/etc/rc.d/cloudflared status;exit 0
parameters:
type:script_output
message:Probing Cloudflare Tunnel
[reconfigure]
command:/bin/sh /usr/local/opnsense/scripts/OPNsense/Cloudflared/reconfigure.sh
parameters:
type:script
message:Reconfiguring Cloudflare Tunnel
[install_binary]
command:/bin/sh /usr/local/opnsense/scripts/OPNsense/Cloudflared/install_binary.sh;exit 0
parameters:
type:script_output
message:Installing Cloudflare Tunnel binary
[tunnel_status]
command:/bin/sh /usr/local/opnsense/scripts/OPNsense/Cloudflared/tunnel_status.sh;exit 0
parameters:
type:script_output
message:Checking Cloudflare Tunnel status

View file

@ -0,0 +1,3 @@
rc.conf.d:/etc/rc.conf.d/cloudflared
cloudflared.token:/usr/local/etc/cloudflared/token
tunables.conf:/usr/local/etc/sysctl.conf.d/cloudflared.conf

View file

@ -0,0 +1 @@
{% if helpers.exists('OPNsense.Cloudflared.general.token') %}{{ OPNsense.Cloudflared.general.token|trim }}{% endif %}

View file

@ -0,0 +1,9 @@
#
# This file is automatically generated. Do not manually edit this file.
#
{% if helpers.exists('OPNsense.Cloudflared.general.enabled') and OPNsense.Cloudflared.general.enabled == '1' %}
cloudflared_enable="YES"
{% else %}
cloudflared_enable="NO"
{% endif %}
cloudflared_mode="tunnel{% if helpers.exists('OPNsense.Cloudflared.general.no_autoupdate') and OPNsense.Cloudflared.general.no_autoupdate == '1' %} --no-autoupdate{% endif %} run{% if helpers.exists('OPNsense.Cloudflared.general.post_quantum') and OPNsense.Cloudflared.general.post_quantum == '1' %} --post-quantum{% endif %}"

View file

@ -0,0 +1,6 @@
#
# This file is automatically generated. Do not manually edit this file.
#
kern.ipc.maxsockbuf={% if helpers.exists('OPNsense.Cloudflared.general.kern_ipc_maxsockbuf') %}{{ OPNsense.Cloudflared.general.kern_ipc_maxsockbuf }}{% else %}16777216{% endif %}
net.inet.udp.recvspace={% if helpers.exists('OPNsense.Cloudflared.general.net_inet_udp_recvspace') %}{{ OPNsense.Cloudflared.general.net_inet_udp_recvspace }}{% else %}8388608{% endif %}

View file

@ -0,0 +1,32 @@
#!/bin/sh
# PROVIDE: cloudflared
# REQUIRE: NETWORKING SERVERS
# KEYWORD: shutdown
. /etc/rc.subr
name="cloudflared"
rcvar="cloudflared_enable"
logfile="/var/log/cloudflared.log"
pidfile="/var/run/cloudflared.pid"
procname="/usr/local/bin/cloudflared"
load_rc_config $name
: ${cloudflared_enable:="NO"}
: ${cloudflared_mode:="tunnel"}
# Load token from secure file
# --metrics é flag global do cloudflared: deve vir antes do subcomando tunnel
if [ -f /usr/local/etc/cloudflared/token ]; then
token=$(cat /usr/local/etc/cloudflared/token)
command_args="--metrics localhost:2000 ${cloudflared_mode} --token ${token}"
else
command_args="--metrics localhost:2000 ${cloudflared_mode}"
fi
command="/usr/sbin/daemon"
command_args="-o ${logfile} -p ${pidfile} -f ${procname} ${command_args}"
run_rc_command "$1"