feat(playout): allow harbor ssl configuration

This commit is contained in:
jo 2023-03-30 20:39:02 +02:00 committed by Kyle Robbertze
parent 8764feded9
commit b2fc3a5ecf
17 changed files with 248 additions and 13 deletions

View File

@ -15,7 +15,17 @@ playout:
liquidsoap:
server_listen_address: 0.0.0.0
harbor_ssl_certificate: /certs/fake.crt
harbor_ssl_private_key: /certs/fake.key
stream:
inputs:
main:
public_url: https://localhost:8001/main
mount: main
port: 8001
secure: true
outputs:
.default_icecast_output: &default_icecast_output
host: icecast

View File

@ -32,6 +32,7 @@ services:
volumes:
- ./playout:/src
- ./dev/playout:/app
- ./dev/certs:/certs
## See https://libretime.org/docs/admin-manual/tutorials/setup-a-pulseaudio-output-inside-containers/
# - ./dev/pulse.socket:/tmp/pulse.socket
# - ./docker/pulse.client.conf:/etc/pulse/client.conf

View File

@ -107,6 +107,13 @@ liquidsoap:
# > default is ["0.0.0.0"]
harbor_listen_address: ["0.0.0.0"]
# Input harbor tls certificate path.
harbor_ssl_certificate:
# Input harbor tls certificate private key path.
harbor_ssl_private_key:
# Input harbor tls certificate password.
harbor_ssl_password:
stream:
# Inputs sources.
inputs:
@ -121,6 +128,9 @@ stream:
# Listen port for the main harbor input.
# > default is 8001
port: 8001
# Whether the input harbor is secured with the tls certificate.
# > default is false
secure: false
# Show harbor input.
show:
@ -133,6 +143,9 @@ stream:
# Listen port for the show harbor input.
# > default is 8002
port: 8002
# Whether the input harbor is secured with the tls certificate.
# > default is false
secure: false
# Output streams.
outputs:

View File

@ -107,6 +107,13 @@ liquidsoap:
# > default is ["0.0.0.0"]
harbor_listen_address: ["0.0.0.0"]
# Input harbor tls certificate path.
harbor_ssl_certificate:
# Input harbor tls certificate private key path.
harbor_ssl_private_key:
# Input harbor tls certificate password.
harbor_ssl_password:
stream:
# Inputs sources.
inputs:
@ -121,6 +128,9 @@ stream:
# Listen port for the main harbor input.
# > default is 8001
port: 8001
# Whether the input harbor is secured with the tls certificate.
# > default is false
secure: false
# Show harbor input.
show:
@ -133,6 +143,9 @@ stream:
# Listen port for the show harbor input.
# > default is 8002
port: 8002
# Whether the input harbor is secured with the tls certificate.
# > default is false
secure: false
# Output streams.
outputs:

View File

@ -107,6 +107,13 @@ liquidsoap:
# > default is ["0.0.0.0"]
harbor_listen_address: ["0.0.0.0"]
# Input harbor tls certificate path.
harbor_ssl_certificate:
# Input harbor tls certificate private key path.
harbor_ssl_private_key:
# Input harbor tls certificate password.
harbor_ssl_password:
stream:
# Inputs sources.
inputs:
@ -121,6 +128,9 @@ stream:
# Listen port for the main harbor input.
# > default is 8001
port: 8001
# Whether the input harbor is secured with the tls certificate.
# > default is false
secure: false
# Show harbor input.
show:
@ -133,6 +143,9 @@ stream:
# Listen port for the show harbor input.
# > default is 8002
port: 8002
# Whether the input harbor is secured with the tls certificate.
# > default is false
secure: false
# Output streams.
outputs:

View File

@ -210,6 +210,13 @@ liquidsoap:
# Input harbor listen address.
# > default is ["0.0.0.0"]
harbor_listen_address: ["0.0.0.0"]
# Input harbor tls certificate path.
harbor_ssl_certificate:
# Input harbor tls certificate private key path.
harbor_ssl_private_key:
# Input harbor tls certificate password.
harbor_ssl_password:
```
## Stream
@ -275,6 +282,9 @@ stream:
# Listen port for the main harbor input.
# > default is 8001
port: 8001
# Whether the input harbor is secured with the tls certificate.
# > default is false
secure: false
# Show harbor input.
show:
@ -287,6 +297,9 @@ stream:
# Listen port for the show harbor input.
# > default is 8002
port: 8002
# Whether the input harbor is secured with the tls certificate.
# > default is false
secure: false
```
### Outputs

View File

@ -360,6 +360,44 @@ Check that the renewal configuration is valid:
sudo certbot renew --dry-run
```
### Setup the certificate for Liquidsoap
To stream audio content from an external source to the LibreTime server, Liquidsoap creates input harbors (Icecast mount points) for the clients to connect to. These mount points are insecure by default, so it's recommended secure them.
To enable the secure input streams, edit the [configuration file](../configuration.md) at `/etc/libretime/config.yml` with the following, be sure to replace `libretime.example.com` with the domain name of your installation:
```git title="/etc/libretime/config.yml"
liquidsoap:
- harbor_ssl_certificate:
- harbor_ssl_private_key:
+ harbor_ssl_certificate: /etc/letsencrypt/live/libretime.example.com/fullchain.pem
+ harbor_ssl_private_key: /etc/letsencrypt/live/libretime.example.com/privkey.pem
```
```git title="/etc/libretime/config.yml"
stream:
inputs:
main:
public_url:
mount: main
port: 8001
- secure: false
+ secure: true
show:
public_url:
mount: show
port: 8002
- secure: false
+ secure: true
```
Restart the LibreTime to apply the changes:
```bash
sudo systemctl restart libretime.target
```
## First login
Once the setup is completed, log in the interface (with the default user `admin` and password `admin`), and edit the project settings (go to **Settings** > **General**) to match your needs.

View File

@ -107,6 +107,13 @@ liquidsoap:
# > default is ["0.0.0.0"]
harbor_listen_address: ["0.0.0.0"]
# Input harbor tls certificate path.
harbor_ssl_certificate:
# Input harbor tls certificate private key path.
harbor_ssl_private_key:
# Input harbor tls certificate password.
harbor_ssl_password:
stream:
# Inputs sources.
inputs:
@ -121,6 +128,9 @@ stream:
# Listen port for the main harbor input.
# > default is 8001
port: 8001
# Whether the input harbor is secured with the tls certificate.
# > default is false
secure: false
# Show harbor input.
show:
@ -133,6 +143,9 @@ stream:
# Listen port for the show harbor input.
# > default is 8002
port: 8002
# Whether the input harbor is secured with the tls certificate.
# > default is false
secure: false
# Output streams.
outputs:

View File

@ -117,6 +117,7 @@ class Schema implements ConfigurationInterface
/* */->validate()->ifString()->then($trim_leading_slash)->end()
/* */->end()
/* */->integerNode('port')->defaultValue(8001)->end()
/* */->booleanNode('secure')->defaultValue(False)->end()
/**/->end()->end()
/**/->arrayNode('show')->addDefaultsIfNotSet()->children()
/* */->booleanNode('enabled')->defaultTrue()->end()
@ -126,6 +127,7 @@ class Schema implements ConfigurationInterface
/* */->validate()->ifString()->then($trim_leading_slash)->end()
/* */->end()
/* */->integerNode('port')->defaultValue(8002)->end()
/* */->booleanNode('secure')->defaultValue(False)->end()
/**/->end()->end()
->end()->end()

View File

@ -1090,8 +1090,11 @@ class Application_Model_Preference
$host = Config::get('general.public_url_raw')->getHost();
$port = Application_Model_StreamSetting::getMasterLiveStreamPort();
$mount = Application_Model_StreamSetting::getMasterLiveStreamMountPoint();
$secure = Application_Model_StreamSetting::getMasterLiveStreamSecure();
return "http://{$host}:{$port}/{$mount}";
$scheme = $secure ? 'https' : 'http';
return "{$scheme}://{$host}:{$port}/{$mount}";
}
public static function GetLiveDJSourceConnectionURL()
@ -1103,8 +1106,11 @@ class Application_Model_Preference
$host = Config::get('general.public_url_raw')->getHost();
$port = Application_Model_StreamSetting::getDjLiveStreamPort();
$mount = Application_Model_StreamSetting::getDjLiveStreamMountPoint();
$secure = Application_Model_StreamSetting::getDjLiveStreamSecure();
return "http://{$host}:{$port}/{$mount}";
$scheme = $secure ? 'https' : 'http';
return "{$scheme}://{$host}:{$port}/{$mount}";
}
public static function SetAutoTransition($value)

View File

@ -190,6 +190,11 @@ class Application_Model_StreamSetting
return Config::get('stream.inputs.main.mount') ?? 'main';
}
public static function getMasterLiveStreamSecure()
{
return Config::get('stream.inputs.main.secure') ?? false;
}
public static function getDjLiveStreamPort()
{
return Config::get('stream.inputs.show.port') ?? 8002;
@ -200,6 +205,11 @@ class Application_Model_StreamSetting
return Config::get('stream.inputs.show.mount') ?? 'show';
}
public static function getDjLiveStreamSecure()
{
return Config::get('stream.inputs.show.secure') ?? false;
}
public static function getAdminUser($stream)
{
return self::getStreamDataNormalized($stream)['admin_user'];

View File

@ -1,5 +1,5 @@
from pathlib import Path
from typing import List, Literal
from typing import List, Literal, Optional
from libretime_shared.config import (
BaseConfig,
@ -7,7 +7,7 @@ from libretime_shared.config import (
RabbitMQConfig,
StreamConfig,
)
from pydantic import BaseModel
from pydantic import BaseModel, root_validator
CACHE_DIR = Path.cwd() / "scheduler"
RECORD_DIR = Path.cwd() / "recorder"
@ -33,6 +33,23 @@ class LiquidsoapConfig(BaseModel):
harbor_listen_address: List[str] = ["0.0.0.0"]
harbor_ssl_certificate: Optional[str] = None
harbor_ssl_private_key: Optional[str] = None
harbor_ssl_password: Optional[str] = None
@root_validator
@classmethod
def _validate_harbor_ssl(cls, values: dict):
harbor_ssl_certificate = values.get("harbor_ssl_certificate")
harbor_ssl_private_key = values.get("harbor_ssl_private_key")
if harbor_ssl_certificate is not None and harbor_ssl_private_key is None:
raise ValueError("missing 'harbor_ssl_private_key' value")
if harbor_ssl_certificate is None and harbor_ssl_private_key is not None:
raise ValueError("missing 'harbor_ssl_certificate' value")
return values
class Config(BaseConfig):
general: GeneralConfig

View File

@ -120,6 +120,14 @@ def input_main_on_disconnect() update_source_status("master_dj", false) end
def input_show_on_connect(header) update_source_status("live_dj", true) end
def input_show_on_disconnect() update_source_status("live_dj", false) end
def make_input_func(secure)
if secure then
input.harbor.ssl
else
input.harbor
end
end
def make_input_auth_handler(input_name)
def auth_handler(user, password)
log("user '#{user}' connected", label="#{input_name}_input")
@ -144,9 +152,10 @@ s = switch(id="switch:blank+schedule",
)
s = if input_show_port != 0 and input_show_mount != "" then
input_show_func = make_input_func(input_show_secure)
input_show_source =
audio_to_stereo(
input.harbor(id="harbor:input_show",
input_show_func(id="harbor:input_show",
input_show_mount,
port=input_show_port,
auth=make_input_auth_handler("show"),
@ -166,9 +175,10 @@ else
end
s = if input_main_port != 0 and input_main_mount != "" then
input_main_func = make_input_func(input_main_secure)
input_main_source =
audio_to_stereo(
input.harbor(id="harbor:input_main",
input_main_func(id="harbor:input_main",
input_main_mount,
port=input_main_port,
auth=make_input_auth_handler("main"),

View File

@ -5,8 +5,10 @@
# Inputs
input_main_mount = {{ config.stream.inputs.main.mount | quote }}
input_main_port = {{ config.stream.inputs.main.port }}
input_main_secure = {{ config.stream.inputs.main.secure | string | lower }}
input_show_mount = {{ config.stream.inputs.show.mount | quote }}
input_show_port = {{ config.stream.inputs.show.port }}
input_show_secure = {{ config.stream.inputs.show.secure | string | lower }}
# Settings
{% if paths.log_filepath is defined -%}
@ -21,6 +23,14 @@ set("server.telnet.port", {{ config.liquidsoap.server_listen_port }})
set("harbor.bind_addrs", ["{{ config.liquidsoap.harbor_listen_address | join('", "') }}"])
{% if config.liquidsoap.harbor_ssl_certificate -%}
set("harbor.ssl.certificate", "{{ config.liquidsoap.harbor_ssl_certificate }}")
set("harbor.ssl.private_key", "{{ config.liquidsoap.harbor_ssl_private_key }}")
{% if config.liquidsoap.harbor_ssl_password -%}
set("harbor.ssl.password", "{{ config.liquidsoap.harbor_ssl_password }}")
{%- endif %}
{% endif -%}
station_name = interactive.string("station_name", {{ info.station_name | quote }})
message_offline = interactive.string("message_offline", {{ preferences.message_offline | quote }})

View File

@ -8,8 +8,10 @@
# Inputs
input_main_mount = "main"
input_main_port = 8001
input_main_secure = false
input_show_mount = "show"
input_show_port = 8002
input_show_secure = false
# Settings
set("log.file.path", "/var/log/radio.log")
@ -43,8 +45,50 @@
# Inputs
input_main_mount = "main"
input_main_port = 8001
input_main_secure = false
input_show_mount = "show"
input_show_port = 8002
input_show_secure = false
# Settings
set("log.file.path", "/var/log/radio.log")
set("server.telnet", true)
set("server.telnet.bind_addr", "127.0.0.1")
set("server.telnet.port", 1234)
set("harbor.bind_addrs", ["0.0.0.0"])
set("harbor.ssl.certificate", "/fake/ssl.cert")
set("harbor.ssl.private_key", "/fake/ssl.key")
station_name = interactive.string("station_name", "LibreTime")
message_offline = interactive.string("message_offline", "LibreTime - offline")
message_format = interactive.string("message_format", "0")
input_fade_transition = interactive.float("input_fade_transition", 0.0)
%include "/fake/1.4/ls_script.liq"
gateway("started")
'''
# ---
# name: test_generate_entrypoint[stream_config2-1.4]
'''
# THIS FILE IS AUTO GENERATED. PLEASE DO NOT EDIT!
###########################################################
# The ignore() lines are to squash unused variable warnings
# Inputs
input_main_mount = "main"
input_main_port = 8001
input_main_secure = false
input_show_mount = "show"
input_show_port = 8002
input_show_secure = false
# Settings
set("log.file.path", "/var/log/radio.log")
@ -94,7 +138,7 @@
'''
# ---
# name: test_generate_entrypoint[stream_config2-1.4]
# name: test_generate_entrypoint[stream_config3-1.4]
'''
# THIS FILE IS AUTO GENERATED. PLEASE DO NOT EDIT!
###########################################################
@ -103,8 +147,10 @@
# Inputs
input_main_mount = "main"
input_main_port = 8001
input_main_secure = false
input_show_mount = "show"
input_show_port = 8002
input_show_secure = false
# Settings
set("log.file.path", "/var/log/radio.log")
@ -150,7 +196,7 @@
'''
# ---
# name: test_generate_entrypoint[stream_config3-1.4]
# name: test_generate_entrypoint[stream_config4-1.4]
'''
# THIS FILE IS AUTO GENERATED. PLEASE DO NOT EDIT!
###########################################################
@ -159,8 +205,10 @@
# Inputs
input_main_mount = "main"
input_main_port = 8001
input_main_secure = false
input_show_mount = "show"
input_show_port = 8002
input_show_secure = false
# Settings
set("log.file.path", "/var/log/radio.log")
@ -231,7 +279,7 @@
'''
# ---
# name: test_generate_entrypoint[stream_config4-1.4]
# name: test_generate_entrypoint[stream_config5-1.4]
'''
# THIS FILE IS AUTO GENERATED. PLEASE DO NOT EDIT!
###########################################################
@ -240,8 +288,10 @@
# Inputs
input_main_mount = "main"
input_main_port = 8001
input_main_secure = false
input_show_mount = "show"
input_show_port = 8002
input_show_secure = false
# Settings
set("log.file.path", "/var/log/radio.log")
@ -274,7 +324,7 @@
'''
# ---
# name: test_generate_entrypoint[stream_config5-1.4]
# name: test_generate_entrypoint[stream_config6-1.4]
'''
# THIS FILE IS AUTO GENERATED. PLEASE DO NOT EDIT!
###########################################################
@ -283,8 +333,10 @@
# Inputs
input_main_mount = "main"
input_main_port = 8001
input_main_secure = false
input_show_mount = "show"
input_show_port = 8002
input_show_secure = false
# Settings
set("log.file.path", "/var/log/radio.log")

View File

@ -3,20 +3,33 @@ from typing import List
from libretime_playout.config import Config
def make_config_with_stream(**kwargs) -> Config:
def make_config(**kwargs) -> Config:
return Config(
**{
"general": {
"public_url": "http://localhost:8080",
"api_key": "some_api_key",
},
"stream": kwargs,
**kwargs,
}
)
def make_config_with_stream(**kwargs) -> Config:
return make_config(stream=kwargs)
TEST_STREAM_CONFIGS: List[Config] = [
make_config_with_stream(),
make_config(),
make_config(
liquidsoap={
"harbor_ssl_certificate": "/fake/ssl.cert",
"harbor_ssl_private_key": "/fake/ssl.key",
},
stream={
"system": [{"enabled": True, "kind": "pulseaudio"}],
},
),
make_config_with_stream(
outputs={
"icecast": [

View File

@ -133,6 +133,7 @@ class HarborInput(BaseInput):
kind: Literal[InputKind.HARBOR] = InputKind.HARBOR
mount: str
port: int
secure: bool = False
_mount_no_leading_slash = no_leading_slash_validator("mount")