Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/docker-security-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:
# Human-readable output in logs
# This NEVER fails the job; it’s only for visibility
- name: Display vulnerabilities (table format)
uses: aquasecurity/trivy-action@0.34.0
uses: aquasecurity/trivy-action@0.35.0
with:
image-ref: torrust-tracker-deployer/${{ matrix.image.name }}:latest
format: "table"
Expand All @@ -76,7 +76,7 @@ jobs:
# - Trivy sometimes exits with 1 even when no vulns exist
# - GitHub Security UI is responsible for enforcement
- name: Generate SARIF (Code Scanning)
uses: aquasecurity/trivy-action@0.34.0
uses: aquasecurity/trivy-action@0.35.0
with:
image-ref: torrust-tracker-deployer/${{ matrix.image.name }}:latest
format: "sarif"
Expand Down Expand Up @@ -114,7 +114,7 @@ jobs:

steps:
- name: Display vulnerabilities (table format)
uses: aquasecurity/trivy-action@0.34.0
uses: aquasecurity/trivy-action@0.35.0
with:
image-ref: ${{ matrix.image }}
format: "table"
Expand All @@ -124,7 +124,7 @@ jobs:
# Third-party images should NEVER block CI.
# We only report findings to GitHub Security.
- name: Generate SARIF (Code Scanning)
uses: aquasecurity/trivy-action@0.34.0
uses: aquasecurity/trivy-action@0.35.0
with:
image-ref: ${{ matrix.image }}
format: "sarif"
Expand Down
9 changes: 9 additions & 0 deletions src/application/services/rendering/docker_compose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,9 +318,18 @@ impl DockerComposeTemplateRenderingService {
/// Apply Grafana credentials to environment context if Grafana is configured
fn apply_grafana_env_context(env_context: EnvContext, user_inputs: &UserInputs) -> EnvContext {
if let Some(grafana_config) = user_inputs.grafana() {
let server_root_url = grafana_config.domain().map(|domain| {
let scheme = if grafana_config.use_tls_proxy() {
"https"
} else {
"http"
};
format!("{scheme}://{}", domain.as_str())
});
env_context.with_grafana(
grafana_config.admin_user().to_string(),
grafana_config.admin_password().expose_secret().to_string(),
server_root_url,
)
} else {
env_context
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ pub struct GrafanaServiceContext {
/// Flattened for template compatibility - serializes ports/networks at top level.
#[serde(flatten)]
pub topology: ServiceTopology,

/// Server root URL for public dashboard share links (optional)
///
/// When set, this is injected as `GF_SERVER_ROOT_URL` env var so that
/// Grafana generates correct absolute URLs for shared dashboards.
/// Derived from the configured domain and TLS setting.
#[serde(skip_serializing_if = "Option::is_none")]
pub server_root_url: Option<String>,
}

impl GrafanaServiceContext {
Expand All @@ -43,8 +51,17 @@ impl GrafanaServiceContext {
.iter()
.map(PortDefinition::from)
.collect();
let server_root_url = config.domain().map(|domain| {
let scheme = if config.use_tls_proxy() {
"https"
} else {
"http"
};
format!("{scheme}://{}", domain.as_str())
});
Self {
topology: ServiceTopology::new(ports, networks),
server_root_url,
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ pub struct GrafanaServiceConfig {
pub admin_user: String,
/// Grafana admin password (exposed from secrecy wrapper)
pub admin_password: String,
/// Grafana server root URL for correct public dashboard share links (optional)
///
/// Derived from the configured domain and TLS setting.
/// Maps to the `GF_SERVER_ROOT_URL` environment variable.
#[serde(skip_serializing_if = "Option::is_none")]
pub server_root_url: Option<String>,
}

/// Context for rendering the .env template
Expand Down Expand Up @@ -186,11 +192,19 @@ impl EnvContext {
///
/// * `admin_user` - Grafana admin username
/// * `admin_password` - Grafana admin password (plain String, already exposed)
/// * `server_root_url` - Optional full URL (e.g. `https://grafana.example.com`) for
/// public dashboard share links; derived from domain + TLS setting
#[must_use]
pub fn with_grafana(mut self, admin_user: String, admin_password: String) -> Self {
pub fn with_grafana(
mut self,
admin_user: String,
admin_password: String,
server_root_url: Option<String>,
) -> Self {
self.grafana = Some(GrafanaServiceConfig {
admin_user,
admin_password,
server_root_url,
});
self
}
Expand Down
4 changes: 4 additions & 0 deletions templates/docker-compose/.env.tera
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,8 @@ MYSQL_PASSWORD='{{ mysql.password }}'
# WARNING: Change default credentials in production deployments for security
GF_SECURITY_ADMIN_USER='{{ grafana.admin_user }}'
GF_SECURITY_ADMIN_PASSWORD='{{ grafana.admin_password }}'
{%- if grafana.server_root_url %}
# Grafana server root URL — used to generate correct public dashboard share links
GF_SERVER_ROOT_URL='{{ grafana.server_root_url }}'
{%- endif %}
{%- endif %}
3 changes: 3 additions & 0 deletions templates/docker-compose/docker-compose.yml.tera
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@ services:
environment:
- GF_SECURITY_ADMIN_USER=${GF_SECURITY_ADMIN_USER}
- GF_SECURITY_ADMIN_PASSWORD=${GF_SECURITY_ADMIN_PASSWORD}
{%- if grafana.server_root_url %}
- GF_SERVER_ROOT_URL=${GF_SERVER_ROOT_URL}
{%- endif %}
volumes:
- ./storage/grafana/data:/var/lib/grafana
- ./storage/grafana/provisioning:/etc/grafana/provisioning:ro
Expand Down
Loading