feat: add full Zonemaster stack with Docker and Spanish UI

- Clone all 5 Zonemaster component repos (LDNS, Engine, CLI, Backend, GUI)
- Dockerfile.backend: 8-stage multi-stage build LDNS→Engine→CLI→Backend
- Dockerfile.gui: Astro static build served via nginx
- docker-compose.yml: backend (internal) + frontend (port 5353)
- nginx.conf: root redirects to /es/, /api/ proxied to backend
- zonemaster-gui/config.ts: defaultLanguage set to 'es' (Spanish)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-21 08:19:24 +02:00
commit 8d4eaa1489
1567 changed files with 204155 additions and 0 deletions

View File

@@ -0,0 +1,240 @@
#!/usr/bin/env perl
use 5.14.2;
use warnings;
use strict;
use version; our $VERSION = version->declare("v1.0.1");
use Carp;
use English qw( -no_match_vars );
use Zonemaster::Engine::Profile;
use JSON::XS;
use Scalar::Util qw( reftype );
use File::Slurp;
use Getopt::Long;
use File::Basename;
use Clone qw(clone);
my $DEBUG = 0;
my $DEST_DIR = q{};
my $CONFIG_FILE = q{};
my $POLICY_FILE = q{};
my $scriptName = basename($PROGRAM_NAME);
my $dest_dir = q{./};
my $config_file = q{};
my $policy_file = q{};
process_options();
if ( $DEST_DIR ) {
$dest_dir = $DEST_DIR;
}
if ( $CONFIG_FILE ) {
$config_file = $CONFIG_FILE;
}
if ( $POLICY_FILE ) {
$policy_file = $POLICY_FILE;
}
if ($DEBUG) {
print "Debug Mode set ON\n";
print "Destination directory : $dest_dir\n\n";
}
#-------------------------------------------------
# STEP 0: Check Directory existence and Directory/Files permissions
#-------------------------------------------------
if ( ! -d $dest_dir ) {
printf "(\"%s --help\" for help)\n", $scriptName;
print "Directory $dest_dir does not exist.\n";
unless ( mkdir $dest_dir ) {
croak "Unable to create $dest_dir.";
}
}
if ( ! -w $dest_dir ) {
printf "(\"%s --help\" for help)\n", $scriptName;
print "Directory $dest_dir mode must be changed.\n";
unless ( chmod (oct(755), $dest_dir) ) {
croak "Cannot change directory mode.";
}
}
if ( ! $config_file ) {
printf "(\"%s --help\" for help)\n", $scriptName;
croak "A Config file must be provided.";
}
if ( ! -e $config_file ) {
printf "(\"%s --help\" for help)\n", $scriptName;
croak "Config file $config_file does not exists.";
}
if ( ! -r $config_file ) {
printf "(\"%s --help\" for help)\n", $scriptName;
croak "Config file $config_file is not readable.";
}
if ( ! $policy_file ) {
printf "(\"%s --help\" for help)\n", $scriptName;
croak "A Policy file must be provided.";
}
if ( ! -e $policy_file ) {
printf "(\"%s --help\" for help)\n", $scriptName;
croak "Policy file $policy_file does not exists.";
}
if ( ! -r $policy_file ) {
printf "(\"%s --help\" for help)\n", $scriptName;
croak "Policy file $policy_file is not readable.";
}
my $policy_json = read_file( $policy_file ) ;
my $policy = decode_json( $policy_json );
my $config_json = read_file( $config_file );
my $config = decode_json( $config_json );
my $default = Zonemaster::Engine::Profile->default;
my $profile = Zonemaster::Engine::Profile->new;
my %paths;
my %default_paths;
Zonemaster::Engine::Profile::_get_profile_paths(\%paths, $config);
Zonemaster::Engine::Profile::_get_profile_paths(\%default_paths, $default->{q{profile}});
delete $default_paths{ q{test_cases} };
delete $default_paths{ q{test_levels} };
#
# General options part
#
foreach my $property_name ( keys %default_paths ) {
my $value = Zonemaster::Engine::Profile::_get_value_from_nested_hash( $config, split /\./, $property_name );
if ( defined $value ) {
$profile->set( $property_name, $value );
}
}
#
# Test cases part
#
my @tc;
foreach my $tc ( @{ $default->get( q{test_cases} ) } ) {
if ( not defined $policy->{__testcases__}->{$tc} or $policy->{__testcases__}->{$tc} ) {
push @tc, $tc;
}
}
$profile->set( q{test_cases}, \@tc );
#
# Test levels part
#
my %tl;
foreach my $tl ( keys %{ $default->get( q{test_levels} ) } ) {
if ( exists $policy->{$tl} ) {
$tl{$tl} = clone $policy->{$tl};
}
}
$profile->set( q{test_levels}, \%tl );
my $filename = $dest_dir.q{/profile.json};
open(my $fh, '>', $filename) or die "Could not open file '$filename' $!";
my $json = JSON::XS->new->canonical->pretty;
print $fh $json->encode( $profile->{q{profile}} );
close $fh;
sub process_options {
my ( $opt_dest, $opt_config, $opt_policy, $opt_help, $opt_debug, $opt_version );
GetOptions(
q{dest-dir=s} => \$opt_dest, # Dest directory for generated profile file
q{config=s} => \$opt_config, # Config File
q{policy=s} => \$opt_policy, # Policy file
q{help} => \$opt_help, # Print Usage
q{debug} => \$opt_debug, # Set Debug MODE
q{version} => \$opt_version, # Print Version
);
if ( $opt_debug ) {
$DEBUG = 1;
}
if ( $opt_dest ) {
$DEST_DIR = $opt_dest;
}
if ( $opt_config ) {
$CONFIG_FILE = $opt_config;
}
if ( $opt_policy ) {
$POLICY_FILE = $opt_policy;
}
if ( $opt_help ) {
Usage();
}
if ( $opt_version ) {
Version();
}
return;
}
sub Usage {
my $_bold = "\e[1m";
my $_normal = "\e[0m";
my $_ul = "\e[4m";
my $scriptNameBlank = $scriptName;
$scriptNameBlank =~ s/./ /smxg;
print << "EOM";
${_bold}NAME${_normal}
${scriptName} - Convert Config/Policy files into Profile file.
${_bold}SYNOPSIS${_normal}
${scriptName} [ --help ] [ --dest-dir=${_ul}alternate_destination_directory${_normal} ] [ --config=${_ul}config_file${_normal} ] [ --policy=${_ul}policy_file${_normal} ] [ --debug ]
${_bold}OPTIONS${_normal}
--help
Print this message and exit.
--config
Name of the Config file to convert.
--policy
Name of the Policy file to convert.
--dest-dir
Name of an alternate directory to save generated profile file.
${_bold}DEFAULT${_normal} is $dest_dir.
--debug
Set Debug mode ON.
--version
Print version of this program.
EOM
exit 0;
}
sub Version {
printf "%s %s\n", $scriptName, $VERSION;
exit 0;
}