#
tokens: 48564/50000 25/140 files (page 2/6)
lines: off (toggle) GitHub
raw markdown copy
This is page 2 of 6. Use http://codebase.md/chillbruhhh/crawl4ai-mcp?page={x} to view the full context.

# Directory Structure

```
├── .dockerignore
├── .env.example
├── .gitattributes
├── .gitignore
├── crawled_pages.sql
├── Dockerfile
├── knowledge_graphs
│   ├── ai_hallucination_detector.py
│   ├── ai_script_analyzer.py
│   ├── hallucination_reporter.py
│   ├── knowledge_graph_validator.py
│   ├── parse_repo_into_neo4j.py
│   ├── query_knowledge_graph.py
│   └── test_script.py
├── LICENSE
├── neo4j
│   └── docker-neo4j
│       ├── .github
│       │   └── ISSUE_TEMPLATE
│       │       └── bug_report.md
│       ├── .gitignore
│       ├── build-docker-image.sh
│       ├── build-utils-common-functions.sh
│       ├── COPYRIGHT
│       ├── DEVELOPMENT.md
│       ├── devenv
│       ├── devenv.local.template
│       ├── docker-image-src
│       │   ├── 2.3
│       │   │   ├── docker-entrypoint.sh
│       │   │   └── Dockerfile
│       │   ├── 3.0
│       │   │   ├── docker-entrypoint.sh
│       │   │   └── Dockerfile
│       │   ├── 3.1
│       │   │   ├── docker-entrypoint.sh
│       │   │   └── Dockerfile
│       │   ├── 3.2
│       │   │   ├── docker-entrypoint.sh
│       │   │   └── Dockerfile
│       │   ├── 3.3
│       │   │   ├── docker-entrypoint.sh
│       │   │   └── Dockerfile
│       │   ├── 3.4
│       │   │   ├── docker-entrypoint.sh
│       │   │   └── Dockerfile
│       │   ├── 3.5
│       │   │   ├── coredb
│       │   │   │   ├── docker-entrypoint.sh
│       │   │   │   ├── Dockerfile
│       │   │   │   └── neo4j-plugins.json
│       │   │   └── neo4j-admin
│       │   │       ├── docker-entrypoint.sh
│       │   │       └── Dockerfile
│       │   ├── 4.0
│       │   │   ├── coredb
│       │   │   │   ├── docker-entrypoint.sh
│       │   │   │   └── Dockerfile
│       │   │   └── neo4j-admin
│       │   │       ├── docker-entrypoint.sh
│       │   │       └── Dockerfile
│       │   ├── 4.1
│       │   │   ├── coredb
│       │   │   │   ├── docker-entrypoint.sh
│       │   │   │   └── Dockerfile
│       │   │   └── neo4j-admin
│       │   │       ├── docker-entrypoint.sh
│       │   │       └── Dockerfile
│       │   ├── 4.2
│       │   │   ├── coredb
│       │   │   │   ├── docker-entrypoint.sh
│       │   │   │   ├── Dockerfile
│       │   │   │   └── neo4j-plugins.json
│       │   │   └── neo4j-admin
│       │   │       ├── docker-entrypoint.sh
│       │   │       └── Dockerfile
│       │   ├── 4.3
│       │   │   ├── coredb
│       │   │   │   ├── docker-entrypoint.sh
│       │   │   │   ├── Dockerfile
│       │   │   │   └── neo4j-plugins.json
│       │   │   └── neo4j-admin
│       │   │       ├── docker-entrypoint.sh
│       │   │       └── Dockerfile
│       │   ├── 4.4
│       │   │   ├── coredb
│       │   │   │   ├── docker-entrypoint.sh
│       │   │   │   ├── Dockerfile-debian
│       │   │   │   ├── Dockerfile-ubi9
│       │   │   │   ├── neo4j-admin-report.sh
│       │   │   │   └── neo4j-plugins.json
│       │   │   └── neo4j-admin
│       │   │       ├── docker-entrypoint.sh
│       │   │       ├── Dockerfile-debian
│       │   │       └── Dockerfile-ubi9
│       │   ├── 5
│       │   │   ├── coredb
│       │   │   │   ├── docker-entrypoint.sh
│       │   │   │   ├── Dockerfile-debian
│       │   │   │   ├── Dockerfile-ubi8
│       │   │   │   ├── Dockerfile-ubi9
│       │   │   │   ├── neo4j-admin-report.sh
│       │   │   │   └── neo4j-plugins.json
│       │   │   └── neo4j-admin
│       │   │       ├── docker-entrypoint.sh
│       │   │       ├── Dockerfile-debian
│       │   │       ├── Dockerfile-ubi8
│       │   │       └── Dockerfile-ubi9
│       │   ├── calver
│       │   │   ├── coredb
│       │   │   │   ├── docker-entrypoint.sh
│       │   │   │   ├── Dockerfile-debian
│       │   │   │   ├── Dockerfile-ubi9
│       │   │   │   ├── neo4j-admin-report.sh
│       │   │   │   └── neo4j-plugins.json
│       │   │   └── neo4j-admin
│       │   │       ├── docker-entrypoint.sh
│       │   │       ├── Dockerfile-debian
│       │   │       └── Dockerfile-ubi9
│       │   └── common
│       │       ├── semver.jq
│       │       └── utilities.sh
│       ├── generate-stub-plugin
│       │   ├── build.gradle.kts
│       │   ├── Dockerfile
│       │   ├── ExampleNeo4jPlugin.java
│       │   ├── Makefile
│       │   ├── README.md
│       │   └── settings.gradle.kts
│       ├── LICENSE
│       ├── Makefile
│       ├── pom.xml
│       ├── publish-neo4j-admin-image.sh
│       ├── publish-neo4j-admin-images.sh
│       ├── README.md
│       └── src
│           ├── main
│           │   └── resources
│           │       └── log4j.properties
│           └── test
│               ├── java
│               │   └── com
│               │       └── neo4j
│               │           └── docker
│               │               ├── coredb
│               │               │   ├── configurations
│               │               │   │   ├── Configuration.java
│               │               │   │   ├── Setting.java
│               │               │   │   ├── TestConfSettings.java
│               │               │   │   ├── TestExtendedConf.java
│               │               │   │   └── TestJVMAdditionalConfig.java
│               │               │   ├── plugins
│               │               │   │   ├── Neo4jPluginEnv.java
│               │               │   │   ├── StubPluginHelper.java
│               │               │   │   ├── TestBundledPluginInstallation.java
│               │               │   │   ├── TestPluginInstallation.java
│               │               │   │   └── TestSemVerPluginMatching.java
│               │               │   ├── TestAdminReport.java
│               │               │   ├── TestAuthentication.java
│               │               │   ├── TestBasic.java
│               │               │   ├── TestCausalCluster.java
│               │               │   ├── TestMounting.java
│               │               │   └── TestUpgrade.java
│               │               ├── neo4jadmin
│               │               │   ├── TestAdminBasic.java
│               │               │   ├── TestBackupRestore.java
│               │               │   ├── TestBackupRestore44.java
│               │               │   ├── TestDumpLoad.java
│               │               │   ├── TestDumpLoad44.java
│               │               │   └── TestReport.java
│               │               ├── TestDeprecationWarning.java
│               │               ├── TestDockerComposeSecrets.java
│               │               └── utils
│               │                   ├── DatabaseIO.java
│               │                   ├── HostFileHttpHandler.java
│               │                   ├── HttpServerTestExtension.java
│               │                   ├── Neo4jVersion.java
│               │                   ├── Neo4jVersionTest.java
│               │                   ├── Network.java
│               │                   ├── SetContainerUser.java
│               │                   ├── TemporaryFolderManager.java
│               │                   ├── TemporaryFolderManagerTest.java
│               │                   ├── TestSettings.java
│               │                   └── WaitStrategies.java
│               └── resources
│                   ├── causal-cluster-compose.yml
│                   ├── confs
│                   │   ├── before50
│                   │   │   ├── ConfsNotOverridden.conf
│                   │   │   ├── ConfsReplaced.conf
│                   │   │   ├── EnterpriseOnlyNotOverwritten.conf
│                   │   │   ├── EnvVarsOverride.conf
│                   │   │   ├── ExtendedConf.conf
│                   │   │   ├── InvalidExtendedConf.conf
│                   │   │   ├── JvmAdditionalNotOverridden.conf
│                   │   │   ├── NoNewline.conf
│                   │   │   └── ReadConf.conf
│                   │   ├── ConfsNotOverridden.conf
│                   │   ├── ConfsReplaced.conf
│                   │   ├── EnterpriseOnlyNotOverwritten.conf
│                   │   ├── EnvVarsOverride.conf
│                   │   ├── ExtendedConf.conf
│                   │   ├── InvalidExtendedConf.conf
│                   │   ├── JvmAdditionalNotOverridden.conf
│                   │   ├── NoNewline.conf
│                   │   └── ReadConf.conf
│                   ├── dockersecrets
│                   │   ├── container-compose-with-incorrect-secrets.yml
│                   │   ├── container-compose-with-secrets-override.yml
│                   │   ├── container-compose-with-secrets.yml
│                   │   ├── simple-container-compose-with-external-file-var.yml
│                   │   └── simple-container-compose.yml
│                   ├── ha-cluster-compose.yml
│                   └── stubplugin
│                       └── myPlugin.jar
├── pyproject.toml
├── README.md
├── src
│   ├── crawl4ai_mcp.py
│   └── utils.py
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/neo4j/docker-neo4j/docker-image-src/calver/neo4j-admin/docker-entrypoint.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/bash -eu

# load useful utility functions
. /startup/utilities.sh

function check_mounted_folder_writable_with_chown
{
    local mountFolder=${1}
    debug_msg "checking ${mountFolder} is writable"
    if running_as_root; then
        # check folder permissions
        if ! is_writable "${mountFolder}" ;  then
            # warn that we're about to chown the folder and then chown it
            echo "Warning: Folder mounted to \"${mountFolder}\" is not writable from inside container. Changing folder owner to ${userid}."
            chown -R "${userid}":"${groupid}" "${mountFolder}"
        # check permissions on files in the folder
        elif [ $(${exec_cmd} find "${mountFolder}" -not -writable | wc -l) -gt 0 ]; then
            echo "Warning: Some files inside \"${mountFolder}\" are not writable from inside container. Changing folder owner to ${userid}."
            chown -R "${userid}":"${groupid}" "${mountFolder}"
        fi
    else
        if [[ ! -w "${mountFolder}" ]]  && [[ "$(stat -c %U ${mountFolder})" != "neo4j" ]]; then
            print_permissions_advice_and_fail "${mountFolder}" "${userid}" "${groupid}"
        fi
    fi
}

# ==== SETUP WHICH USER TO RUN AS ====
debug_msg "DEBUGGING ENABLED"

if running_as_root; then
  userid="neo4j"
  groupid="neo4j"
  groups=($(id -G neo4j))
  exec_cmd="runuser -p -u neo4j -g neo4j --"
  debug_msg "Running as root user inside neo4j-admin image"
else
  userid="$(id -u)"
  groupid="$(id -g)"
  groups=($(id -G))
  exec_cmd="exec"
  debug_msg "Running as user ${userid} inside neo4j-admin image"
fi

#%%DEPRECATION_WARNING_PLACEHOLDER%%

# ==== MAKE SURE NEO4J CANNOT BE RUN FROM THIS CONTAINER ====
debug_msg "checking neo4j was not requested"
if [[ "${1}" == "neo4j" ]]; then
    correct_image="neo4j:"$(neo4j-admin --version)"-${NEO4J_EDITION}"
    echo >&2 "
This is a neo4j-admin only image, and usage of Neo4j server is not supported from here.
If you wish to start a Neo4j database, use:

docker run ${correct_image}
    "
    exit 1
fi

# ==== MAKE SURE NEO4J-ADMIN REPORT CANNOT BE RUN FROM THIS CONTAINER ====
debug_msg "checking neo4j-admin report was not requested"
# maybe make sure the command is neo4j-admin server report rather than just anything mentioning reports?
if containsElement "report" "${@}"; then
    echo >&2 \
"neo4j-admin report must be run in the same container as neo4j
otherwise the report tool cannot access relevant files and processes required for generating the report.

To run the report tool inside a neo4j container, do:

docker exec <CONTAINER NAME> neo4j-admin-report

"
    exit 1
fi


# ==== CHECK LICENSE AGREEMENT ====

debug_msg "checking license"
# Only prompt for license agreement if command contains "neo4j" in it
if [[ "${1}" == *"neo4j"* ]]; then
    if [ "${NEO4J_EDITION}" == "enterprise" ]; then
        : ${NEO4J_ACCEPT_LICENSE_AGREEMENT:="not accepted"}
        if [[ "$NEO4J_ACCEPT_LICENSE_AGREEMENT" != "yes" && "$NEO4J_ACCEPT_LICENSE_AGREEMENT" != "eval" ]]; then
            echo >&2 "
In order to use Neo4j Enterprise Edition you must accept the license agreement.

The license agreement is available at https://neo4j.com/terms/licensing/
If you have a support contract the following terms apply https://neo4j.com/terms/support-terms/

If you do not have a commercial license and want to evaluate the Software
please read the terms of the evaluation agreement before you accept.
https://neo4j.com/terms/enterprise_us/

(c) Neo4j Sweden AB. All Rights Reserved.
Use of this Software without a proper commercial license, or evaluation license
with Neo4j, Inc. or its affiliates is prohibited.
Neo4j has the right to terminate your usage if you are not compliant.

More information is also available at: https://neo4j.com/licensing/
If you have further inquiries about licensing, please contact us via https://neo4j.com/contact-us/

To accept the commercial license agreement set the environment variable
NEO4J_ACCEPT_LICENSE_AGREEMENT=yes

To accept the terms of the evaluation agreement set the environment variable
NEO4J_ACCEPT_LICENSE_AGREEMENT=eval

To do this you can use the following docker argument:

        --env=NEO4J_ACCEPT_LICENSE_AGREEMENT=<yes|eval>
"
            exit 1
        fi
    fi
fi

# ==== ENSURE MOUNT FOLDER READ/WRITABILITY ====
debug_msg "Checking for mounted folder writability"

if [ -d /data ]; then
    check_mounted_folder_writable_with_chown "/data"
    if [ -d /data/databases ]; then
        check_mounted_folder_writable_with_chown "/data/databases"
    fi
    if [ -d /data/dbms ]; then
        check_mounted_folder_writable_with_chown "/data/dbms"
    fi
    if [ -d /data/transactions ]; then
        check_mounted_folder_writable_with_chown "/data/transactions"
    fi
fi
if [ -d /backups ]; then
    check_mounted_folder_writable_with_chown "/backups"
fi

# ==== START NEO4J-ADMIN COMMAND ====
if debugging_enabled; then
    echo ${exec_cmd} "${@}" --verbose
    ${exec_cmd} "${@}" --verbose
else
    ${exec_cmd} "${@}"
fi

```

--------------------------------------------------------------------------------
/crawled_pages.sql:
--------------------------------------------------------------------------------

```sql
-- Enable the pgvector extension
create extension if not exists vector;

-- Drop tables if they exist (to allow rerunning the script)
drop table if exists crawled_pages;
drop table if exists code_examples;
drop table if exists sources;

-- Create the sources table
create table sources (
    source_id text primary key,
    summary text,
    total_word_count integer default 0,
    created_at timestamp with time zone default timezone('utc'::text, now()) not null,
    updated_at timestamp with time zone default timezone('utc'::text, now()) not null
);

-- Create the documentation chunks table
create table crawled_pages (
    id bigserial primary key,
    url varchar not null,
    chunk_number integer not null,
    content text not null,
    metadata jsonb not null default '{}'::jsonb,
    source_id text not null,
    embedding vector(1536),  -- OpenAI embeddings are 1536 dimensions
    created_at timestamp with time zone default timezone('utc'::text, now()) not null,
    
    -- Add a unique constraint to prevent duplicate chunks for the same URL
    unique(url, chunk_number),
    
    -- Add foreign key constraint to sources table
    foreign key (source_id) references sources(source_id)
);

-- Create an index for better vector similarity search performance
create index on crawled_pages using ivfflat (embedding vector_cosine_ops);

-- Create an index on metadata for faster filtering
create index idx_crawled_pages_metadata on crawled_pages using gin (metadata);

-- Create an index on source_id for faster filtering
CREATE INDEX idx_crawled_pages_source_id ON crawled_pages (source_id);

-- Create a function to search for documentation chunks
create or replace function match_crawled_pages (
  query_embedding vector(1536),
  match_count int default 10,
  filter jsonb DEFAULT '{}'::jsonb,
  source_filter text DEFAULT NULL
) returns table (
  id bigint,
  url varchar,
  chunk_number integer,
  content text,
  metadata jsonb,
  source_id text,
  similarity float
)
language plpgsql
as $$
#variable_conflict use_column
begin
  return query
  select
    id,
    url,
    chunk_number,
    content,
    metadata,
    source_id,
    1 - (crawled_pages.embedding <=> query_embedding) as similarity
  from crawled_pages
  where metadata @> filter
    AND (source_filter IS NULL OR source_id = source_filter)
  order by crawled_pages.embedding <=> query_embedding
  limit match_count;
end;
$$;

-- Enable RLS on the crawled_pages table
alter table crawled_pages enable row level security;

-- Create a policy that allows anyone to read crawled_pages
create policy "Allow public read access to crawled_pages"
  on crawled_pages
  for select
  to public
  using (true);

-- Enable RLS on the sources table
alter table sources enable row level security;

-- Create a policy that allows anyone to read sources
create policy "Allow public read access to sources"
  on sources
  for select
  to public
  using (true);

-- Create the code_examples table
create table code_examples (
    id bigserial primary key,
    url varchar not null,
    chunk_number integer not null,
    content text not null,  -- The code example content
    summary text not null,  -- Summary of the code example
    metadata jsonb not null default '{}'::jsonb,
    source_id text not null,
    embedding vector(1536),  -- OpenAI embeddings are 1536 dimensions
    created_at timestamp with time zone default timezone('utc'::text, now()) not null,
    
    -- Add a unique constraint to prevent duplicate chunks for the same URL
    unique(url, chunk_number),
    
    -- Add foreign key constraint to sources table
    foreign key (source_id) references sources(source_id)
);

-- Create an index for better vector similarity search performance
create index on code_examples using ivfflat (embedding vector_cosine_ops);

-- Create an index on metadata for faster filtering
create index idx_code_examples_metadata on code_examples using gin (metadata);

-- Create an index on source_id for faster filtering
CREATE INDEX idx_code_examples_source_id ON code_examples (source_id);

-- Create a function to search for code examples
create or replace function match_code_examples (
  query_embedding vector(1536),
  match_count int default 10,
  filter jsonb DEFAULT '{}'::jsonb,
  source_filter text DEFAULT NULL
) returns table (
  id bigint,
  url varchar,
  chunk_number integer,
  content text,
  summary text,
  metadata jsonb,
  source_id text,
  similarity float
)
language plpgsql
as $$
#variable_conflict use_column
begin
  return query
  select
    id,
    url,
    chunk_number,
    content,
    summary,
    metadata,
    source_id,
    1 - (code_examples.embedding <=> query_embedding) as similarity
  from code_examples
  where metadata @> filter
    AND (source_filter IS NULL OR source_id = source_filter)
  order by code_examples.embedding <=> query_embedding
  limit match_count;
end;
$$;

-- Enable RLS on the code_examples table
alter table code_examples enable row level security;

-- Create a policy that allows anyone to read code_examples
create policy "Allow public read access to code_examples"
  on code_examples
  for select
  to public
  using (true);
```

--------------------------------------------------------------------------------
/neo4j/docker-neo4j/src/test/java/com/neo4j/docker/coredb/configurations/Configuration.java:
--------------------------------------------------------------------------------

```java
package com.neo4j.docker.coredb.configurations;

import com.neo4j.docker.utils.Neo4jVersion;
import com.neo4j.docker.utils.TestSettings;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.EnumMap;
import java.util.Map;

public class Configuration
{
    private static Map<Setting,Configuration> CONFIGURATIONS_5X = new EnumMap<Setting,Configuration>( Setting.class ) {{
        put( Setting.APOC_EXPORT_FILE_ENABLED, new Configuration( "apoc.export.file.enabled"));
        put( Setting.BACKUP_ENABLED, new Configuration("server.backup.enabled"));
        put( Setting.BACKUP_LISTEN_ADDRESS, new Configuration("server.backup.listen_address"));
        put( Setting.BROWSER_ALLOW_OUTGOING_CONNECTIONS, new Configuration("browser.allow_outgoing_connections"));
        put( Setting.CLUSTER_RAFT_ADDRESS, new Configuration("server.cluster.raft.advertised_address"));
        put( Setting.CLUSTER_ROUTING_ADDRESS, new Configuration("server.routing.advertised_address"));
        put( Setting.CLUSTER_TRANSACTION_ADDRESS, new Configuration("server.cluster.advertised_address"));
        put( Setting.DEFAULT_LISTEN_ADDRESS, new Configuration("server.default_listen_address"));
        put( Setting.DIRECTORIES_DATA, new Configuration("server.directories.data"));
        put( Setting.DIRECTORIES_LOGS, new Configuration("server.directories.logs"));
        put( Setting.DIRECTORIES_METRICS, new Configuration("server.directories.metrics"));
        put( Setting.JVM_ADDITIONAL, new Configuration("server.jvm.additional"));
        put( Setting.LOGS_GC_ROTATION_KEEPNUMBER, new Configuration( "server.logs.gc.rotation.keep_number"));
        put( Setting.MEMORY_HEAP_INITIALSIZE, new Configuration("server.memory.heap.initial_size"));
        put( Setting.MEMORY_HEAP_MAXSIZE, new Configuration( "server.memory.heap.max_size"));
        put( Setting.MEMORY_PAGECACHE_SIZE, new Configuration("server.memory.pagecache.size"));
        put( Setting.MINIMUM_PASSWORD_LENGTH, new Configuration("dbms.security.auth_minimum_password_length"));
        put( Setting.SECURITY_PROCEDURES_UNRESTRICTED, new Configuration("dbms.security.procedures.unrestricted"));
        put( Setting.TXLOG_RETENTION_POLICY, new Configuration("db.tx_log.rotation.retention_policy"));
    }};

    private static Map<Setting,Configuration> CONFIGURATIONS_4X = new EnumMap<Setting,Configuration>( Setting.class ) {{
        put( Setting.APOC_EXPORT_FILE_ENABLED, new Configuration( "apoc.export.file.enabled"));
        put( Setting.BACKUP_ENABLED, new Configuration("dbms.backup.enabled"));
        put( Setting.BACKUP_LISTEN_ADDRESS, new Configuration("dbms.backup.listen_address"));
        put( Setting.BROWSER_ALLOW_OUTGOING_CONNECTIONS, new Configuration("browser.allow_outgoing_connections"));
        put( Setting.CLUSTER_DISCOVERY_ADDRESS, new Configuration("causal_clustering.discovery_advertised_address"));
        put( Setting.CLUSTER_RAFT_ADDRESS, new Configuration("causal_clustering.raft_advertised_address"));
        put( Setting.CLUSTER_ROUTING_ADDRESS, new Configuration("dbms.routing.advertised_address"));
        put( Setting.CLUSTER_TRANSACTION_ADDRESS, new Configuration("causal_clustering.transaction_advertised_address"));
        put( Setting.DEFAULT_LISTEN_ADDRESS, new Configuration("dbms.default_listen_address"));
        put( Setting.DIRECTORIES_DATA, new Configuration("dbms.directories.data"));
        put( Setting.DIRECTORIES_LOGS, new Configuration("dbms.directories.logs"));
        put( Setting.DIRECTORIES_METRICS, new Configuration("dbms.directories.metrics"));
        put( Setting.JVM_ADDITIONAL, new Configuration("dbms.jvm.additional"));
        put( Setting.LOGS_GC_ROTATION_KEEPNUMBER, new Configuration( "dbms.logs.gc.rotation.keep_number"));
        put( Setting.MEMORY_HEAP_INITIALSIZE, new Configuration("dbms.memory.heap.initial_size"));
        put( Setting.MEMORY_HEAP_MAXSIZE, new Configuration("dbms.memory.heap.max_size"));
        put( Setting.MEMORY_PAGECACHE_SIZE, new Configuration("dbms.memory.pagecache.size"));
        put( Setting.SECURITY_PROCEDURES_UNRESTRICTED, new Configuration("dbms.security.procedures.unrestricted"));
        put( Setting.TXLOG_RETENTION_POLICY, new Configuration("dbms.tx_log.rotation.retention_policy"));
    }};
    public static Map<Setting,Configuration> getConfigurationNameMap()
    {
        return getConfigurationNameMap( TestSettings.NEO4J_VERSION );
    }

    public static Map<Setting,Configuration> getConfigurationNameMap( Neo4jVersion version )
    {
        switch ( version.major )
        {
        case 3:
            EnumMap<Setting,Configuration> out = new EnumMap<Setting,Configuration>( CONFIGURATIONS_4X );
            out.put( Setting.DEFAULT_LISTEN_ADDRESS, new Configuration( "dbms.connectors.default_listen_address" ) );
            return out;
        case 4:
            return CONFIGURATIONS_4X;
        default:
            return CONFIGURATIONS_5X;
        }
    }
    public static Path getConfigurationResourcesFolder()
    {
        return getConfigurationResourcesFolder(TestSettings.NEO4J_VERSION);
    }

    public static Path getConfigurationResourcesFolder( Neo4jVersion version )
    {
        if(version.isAtLeastVersion( Neo4jVersion.NEO4J_VERSION_500 ))
        {
        return Paths.get("src", "test", "resources", "confs");
        }
        else return Paths.get( "src", "test", "resources", "confs", "before50");
    }

    public String name;
    public String envName;

    private Configuration( String name )
    {
        this.name = name;
        this.envName = "NEO4J_" + name.replace( '_', '-' )
                                      .replace( '.', '_')
                                      .replace( "-", "__" );
    }
}

```

--------------------------------------------------------------------------------
/neo4j/docker-neo4j/docker-image-src/3.0/docker-entrypoint.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/bash -eu

setting() {
    setting="${1}"
    value="${2}"
    file="${3:-neo4j.conf}"

    if [ ! -f "conf/${file}" ]; then
        if [ -f "conf/neo4j.conf" ]; then
            file="neo4j.conf"
        fi
    fi

    # Don't allow settings with no value or settings that start with a number (neo4j converts settings to env variables and you cannot have an env variable that starts with a number)
    if [[ -n ${value} ]]; then
        if [[ ! "${setting}" =~ ^[0-9]+.*$ ]]; then
            if grep --quiet --fixed-strings "${setting}=" conf/"${file}"; then
                sed --in-place "s|.*${setting}=.*|${setting}=${value}|" conf/"${file}"
            else
                echo "${setting}=${value}" >>conf/"${file}"
            fi
        else
            echo >&2 "WARNING: ${setting} not written to conf file because settings that start with a number are not permitted"
        fi
    fi
}

cmd="$1"

if [ "${cmd}" == "dump-config" ]; then
    if [ -d /conf ]; then
        cp --recursive conf/* /conf
        exit 0
    else
        echo >&2 "You must provide a /conf volume"
        exit 1
    fi
fi

# Env variable naming convention:
# - prefix NEO4J_
# - double underscore char '__' instead of single underscore '_' char in the setting name
# - underscore char '_' instead of dot '.' char in the setting name
# Example:
# NEO4J_dbms_tx__log_rotation_retention__policy env variable to set
#       dbms.tx_log.rotation.retention_policy setting

# Backward compatibility - map old hardcoded env variables into new naming convention (if they aren't set already)
# Set some to default values if unset
: ${NEO4J_dbms_tx__log_rotation_retention__policy:=${NEO4J_dbms_txLog_rotation_retentionPolicy:-"100M size"}}
: ${NEO4J_wrapper_java_additional:=${NEO4J_UDC_SOURCE:-"-Dneo4j.ext.udc.source=docker"}}
: ${NEO4J_dbms_memory_heap_initial__size:=${NEO4J_dbms_memory_heap_maxSize:-"512"}}
: ${NEO4J_dbms_memory_heap_max__size:=${NEO4J_dbms_memory_heap_maxSize:-"512"}}
: ${NEO4J_dbms_unmanaged__extension__classes:=${NEO4J_dbms_unmanagedExtensionClasses:-}}
: ${NEO4J_dbms_allow__format__migration:=${NEO4J_dbms_allowFormatMigration:-}}
: ${NEO4J_ha_server__id:=${NEO4J_ha_serverId:-}}
: ${NEO4J_ha_initial__hosts:=${NEO4J_ha_initialHosts:-}}

: ${NEO4J_dbms_connector_http_address:="0.0.0.0:7474"}
: ${NEO4J_dbms_connector_https_address:="0.0.0.0:7473"}
: ${NEO4J_dbms_connector_bolt_address:="0.0.0.0:7687"}
: ${NEO4J_ha_host_coordination:="$(hostname):5001"}
: ${NEO4J_ha_host_data:="$(hostname):6001"}

# unset old hardcoded unsupported env variables
unset NEO4J_dbms_txLog_rotation_retentionPolicy NEO4J_UDC_SOURCE \
    NEO4J_dbms_memory_heap_maxSize NEO4J_dbms_memory_heap_maxSize \
    NEO4J_dbms_unmanagedExtensionClasses NEO4J_dbms_allowFormatMigration \
    NEO4J_ha_initialHosts

if [ -d /conf ]; then
    find /conf -type f -exec cp {} conf \;
fi

if [ -d /ssl ]; then
    NEO4J_dbms_directories_certificates="/ssl"
fi

if [ -d /plugins ]; then
    NEO4J_dbms_directories_plugins="/plugins"
fi

if [ -d /logs ]; then
    NEO4J_dbms_directories_logs="/logs"
fi

if [ -d /import ]; then
    NEO4J_dbms_directories_import="/import"
fi

if [ -d /metrics ]; then
    NEO4J_dbms_directories_metrics="/metrics"
fi

if [ "${cmd}" == "neo4j" ] ; then
    if [ "${NEO4J_AUTH:-}" == "none" ]; then
        NEO4J_dbms_security_auth__enabled=false
    elif [[ "${NEO4J_AUTH:-}" == neo4j/* ]]; then
        password="${NEO4J_AUTH#neo4j/}"
        if [ "${password}" == "neo4j" ]; then
            echo "Invalid value for password. It cannot be 'neo4j', which is the default."
            exit 1
        fi

        setting "dbms.connector.http.address" "127.0.0.1:7474"
        setting "dbms.connector.https.address" "127.0.0.1:7473"
        setting "dbms.connector.bolt.address" "127.0.0.1:7687"
        bin/neo4j start || \
            (cat logs/neo4j.log && echo "Neo4j failed to start for password change" && exit 1)

        end="$((SECONDS+100))"
        while true; do
            http_code="$(curl --silent --write-out %{http_code} --user "neo4j:${password}" --output /dev/null http://localhost:7474/db/data/ || true)"

            if [[ "${http_code}" = "200" ]]; then
                break;
            fi

            if [[ "${http_code}" = "401" ]]; then
                curl --fail --silent --show-error --user neo4j:neo4j \
                     --data '{"password": "'"${password}"'"}' \
                     --header 'Content-Type: application/json' \
                     http://localhost:7474/user/neo4j/password
                break;
            fi

            if [[ "${SECONDS}" -ge "${end}" ]]; then
                (cat logs/neo4j.log && echo "Neo4j failed to start" && exit 1)
            fi

            sleep 1
        done

        bin/neo4j stop
    elif [ -n "${NEO4J_AUTH:-}" ]; then
        echo "Invalid value for NEO4J_AUTH: '${NEO4J_AUTH}'"
        exit 1
    fi
fi

# list env variables with prefix NEO4J_ and create settings from them
unset NEO4J_AUTH NEO4J_SHA256 NEO4J_TARBALL
for i in $( set | grep ^NEO4J_ | awk -F'=' '{print $1}' | sort -rn ); do
    setting=$(echo ${i} | sed 's|^NEO4J_||' | sed 's|_|.|g' | sed 's|\.\.|_|g')
    value=$(echo ${!i})
    if [[ -n ${value} ]]; then
        if [[ ! "${setting}" =~ ^[0-9]+.*$ ]]; then
            if grep -q -F "${setting}=" conf/neo4j.conf; then
                # Remove any lines containing the setting already
                sed --in-place "/^${setting}=.*/d" conf/neo4j.conf
            fi
            # Then always append setting to file
            echo "${setting}=${value}" >> conf/neo4j.conf
        else
            echo >&2 "WARNING: ${setting} not written to conf file because settings that start with a number are not permitted"
        fi
    fi
done

[ -f "${EXTENSION_SCRIPT:-}" ] && . ${EXTENSION_SCRIPT}

if [ "${cmd}" == "neo4j" ] ; then
    exec bin/neo4j console
else
    exec "$@"
fi

```

--------------------------------------------------------------------------------
/neo4j/docker-neo4j/src/test/java/com/neo4j/docker/neo4jadmin/TestDumpLoad.java:
--------------------------------------------------------------------------------

```java
package com.neo4j.docker.neo4jadmin;

import com.neo4j.docker.utils.DatabaseIO;
import com.neo4j.docker.utils.Neo4jVersion;
import com.neo4j.docker.utils.SetContainerUser;
import com.neo4j.docker.utils.WaitStrategies;
import com.neo4j.docker.utils.TemporaryFolderManager;
import com.neo4j.docker.utils.TestSettings;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;
import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;

import java.nio.file.Path;
import java.time.Duration;

public class TestDumpLoad
{
    private static Logger log = LoggerFactory.getLogger( TestDumpLoad.class );
    @RegisterExtension
    public static TemporaryFolderManager temporaryFolderManager = new TemporaryFolderManager();

    @BeforeAll
    static void beforeAll()
    {
        Assumptions.assumeTrue( TestSettings.NEO4J_VERSION.isAtLeastVersion( Neo4jVersion.NEO4J_VERSION_500 ),
                                "These tests only apply to neo4j-admin images of 5.0 and greater");
    }

    private GenericContainer createDBContainer( boolean asDefaultUser, String password )
    {
        String auth = "none";
        if(!password.equalsIgnoreCase("none"))
        {
            auth = "neo4j/"+password;
        }

        GenericContainer container = new GenericContainer( TestSettings.IMAGE_ID );
        container.withEnv( "NEO4J_AUTH", auth )
                 .withEnv( "NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes" )
                 .withExposedPorts( 7474, 7687 )
                 .withLogConsumer( new Slf4jLogConsumer( log ) )
                 .waitingFor( WaitStrategies.waitForNeo4jReady( password) );
        if(!asDefaultUser)
        {
            SetContainerUser.nonRootUser( container );
        }
        return container;
    }

    private GenericContainer createAdminContainer( boolean asDefaultUser )
    {
        GenericContainer container = new GenericContainer( TestSettings.ADMIN_IMAGE_ID );
        container.withEnv( "NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes" )
                 .withExposedPorts( 7474, 7687 )
                 .withLogConsumer( new Slf4jLogConsumer( log ) )
                 .waitingFor( new LogMessageWaitStrategy().withRegEx( "^Done: \\d+ files, [\\d\\.,]+[KMGi]*B processed.*" ) )
//                 .waitingFor( new LogMessageWaitStrategy().withRegEx( "^Done: .*" ) )
                 .withStartupCheckStrategy( new OneShotStartupCheckStrategy().withTimeout( Duration.ofSeconds( 90 ) ) );
        if(!asDefaultUser)
        {
            SetContainerUser.nonRootUser( container );
        }
        return container;
    }

    @Test
    void shouldDumpAndLoad_defaultUser_noAuth() throws Exception
    {
        shouldCreateDumpAndLoadDump( true, "none" );
    }

    @Test
    void shouldDumpAndLoad_nonDefaultUser_noAuth() throws Exception
    {
        shouldCreateDumpAndLoadDump( false, "none" );
    }

    @Test
    void shouldDumpAndLoad_defaultUser_withAuth() throws Exception
    {
        shouldCreateDumpAndLoadDump( true, "verysecretpassword" );
    }

    @Test
    void shouldDumpAndLoad_nonDefaultUser_withAuth() throws Exception
    {
        shouldCreateDumpAndLoadDump( false, "verysecretpassword" );
    }

    private void shouldCreateDumpAndLoadDump( boolean asDefaultUser, String password ) throws Exception
    {
        Path firstDataDir;
        Path secondDataDir;
        Path backupDir;

        // start a database and populate it
        try(GenericContainer container = createDBContainer( asDefaultUser, password ))
        {
            firstDataDir = temporaryFolderManager.createNamedFolderAndMountAsVolume(container,"data1", "/data");
            container.start();
            DatabaseIO dbio = new DatabaseIO( container );
            dbio.putInitialDataIntoContainer( "neo4j", password );
            container.getDockerClient().stopContainerCmd( container.getContainerId() ).withTimeout(30).exec();
        }

        // use admin container to create dump
        try(GenericContainer admin = createAdminContainer( asDefaultUser ))
        {
            temporaryFolderManager.mountHostFolderAsVolume( admin, firstDataDir, "/data" );
            backupDir = temporaryFolderManager.createFolderAndMountAsVolume(admin, "/backups");
            admin.withCommand( "neo4j-admin", "database", "dump", "neo4j", "--to-path=/backups" );
            admin.start();
        }
        Assertions.assertTrue( backupDir.resolve( "neo4j.dump" ).toFile().exists(), "dump file not created");

        // dump file exists. Now try to load it into a new database.
        // use admin container to create dump
        try(GenericContainer admin = createAdminContainer( asDefaultUser ))
        {
            secondDataDir = temporaryFolderManager.createNamedFolderAndMountAsVolume(admin, "data2", "/data");
            temporaryFolderManager.mountHostFolderAsVolume( admin, backupDir, "/backups" );
            admin.withCommand( "neo4j-admin", "database", "load", "neo4j", "--from-path=/backups" );
            admin.start();
        }

        // verify data in 2nd data directory by starting a database and verifying data we populated earlier
        try(GenericContainer container = createDBContainer( asDefaultUser, password ))
        {
            temporaryFolderManager.mountHostFolderAsVolume( container, secondDataDir, "/data" );
            container.start();
            DatabaseIO dbio = new DatabaseIO( container );
            dbio.verifyInitialDataInContainer( "neo4j", password );
        }
    }
}

```

--------------------------------------------------------------------------------
/knowledge_graphs/test_script.py:
--------------------------------------------------------------------------------

```python
from __future__ import annotations
from typing import Dict, List, Optional
from dataclasses import dataclass
from pydantic import BaseModel, Field
from dotenv import load_dotenv
from rich.markdown import Markdown
from rich.console import Console
from rich.live import Live
import asyncio
import os

from pydantic_ai.providers.openai import OpenAIProvider
from pydantic_ai.models.openai import OpenAIModel
from pydantic_ai import Agent, RunContext
from graphiti_core import Graphiti

load_dotenv()

# ========== Define dependencies ==========
@dataclass
class GraphitiDependencies:
    """Dependencies for the Graphiti agent."""
    graphiti_client: Graphiti

# ========== Helper function to get model configuration ==========
def get_model():
    """Configure and return the LLM model to use."""
    model_choice = os.getenv('MODEL_CHOICE', 'gpt-4.1-mini')
    api_key = os.getenv('OPENAI_API_KEY', 'no-api-key-provided')

    return OpenAIModel(model_choice, provider=OpenAIProvider(api_key=api_key))

# ========== Create the Graphiti agent ==========
graphiti_agent = Agent(
    get_model(),
    system_prompt="""You are a helpful assistant with access to a knowledge graph filled with temporal data about LLMs.
    When the user asks you a question, use your search tool to query the knowledge graph and then answer honestly.
    Be willing to admit when you didn't find the information necessary to answer the question.""",
    deps_type=GraphitiDependencies
)

# ========== Define a result model for Graphiti search ==========
class GraphitiSearchResult(BaseModel):
    """Model representing a search result from Graphiti."""
    uuid: str = Field(description="The unique identifier for this fact")
    fact: str = Field(description="The factual statement retrieved from the knowledge graph")
    valid_at: Optional[str] = Field(None, description="When this fact became valid (if known)")
    invalid_at: Optional[str] = Field(None, description="When this fact became invalid (if known)")
    source_node_uuid: Optional[str] = Field(None, description="UUID of the source node")

# ========== Graphiti search tool ==========
@graphiti_agent.tool
async def search_graphiti(ctx: RunContext[GraphitiDependencies], query: str) -> List[GraphitiSearchResult]:
    """Search the Graphiti knowledge graph with the given query.
    
    Args:
        ctx: The run context containing dependencies
        query: The search query to find information in the knowledge graph
        
    Returns:
        A list of search results containing facts that match the query
    """
    # Access the Graphiti client from dependencies
    graphiti = ctx.deps.graphiti_client
    
    try:
        # Perform the search
        results = await graphiti.search(query)
        
        # Format the results
        formatted_results = []
        for result in results:
            formatted_result = GraphitiSearchResult(
                uuid=result.uuid,
                fact=result.fact,
                source_node_uuid=result.source_node_uuid if hasattr(result, 'source_node_uuid') else None
            )
            
            # Add temporal information if available
            if hasattr(result, 'valid_at') and result.valid_at:
                formatted_result.valid_at = str(result.valid_at)
            if hasattr(result, 'invalid_at') and result.invalid_at:
                formatted_result.invalid_at = str(result.invalid_at)
            
            formatted_results.append(formatted_result)
        
        return formatted_results
    except Exception as e:
        # Log the error but don't close the connection since it's managed by the dependency
        print(f"Error searching Graphiti: {str(e)}")
        raise

# ========== Main execution function ==========
async def main():
    """Run the Graphiti agent with user queries."""
    print("Graphiti Agent - Powered by Pydantic AI, Graphiti, and Neo4j")
    print("Enter 'exit' to quit the program.")

    # Neo4j connection parameters
    neo4j_uri = os.environ.get('NEO4J_URI', 'bolt://localhost:7687')
    neo4j_user = os.environ.get('NEO4J_USER', 'neo4j')
    neo4j_password = os.environ.get('NEO4J_PASSWORD', 'password')
    
    # Initialize Graphiti with Neo4j connection
    graphiti_client = Graphiti(neo4j_uri, neo4j_user, neo4j_password)
    
    # Initialize the graph database with graphiti's indices if needed
    try:
        await graphiti_client.build_indices_and_constraints()
        print("Graphiti indices built successfully.")
    except Exception as e:
        print(f"Note: {str(e)}")
        print("Continuing with existing indices...")

    console = Console()
    messages = []
    
    try:
        while True:
            # Get user input
            user_input = input("\n[You] ")
            
            # Check if user wants to exit
            if user_input.lower() in ['exit', 'quit', 'bye', 'goodbye']:
                print("Goodbye!")
                break
            
            try:
                # Process the user input and output the response
                print("\n[Assistant]")
                with Live('', console=console, vertical_overflow='visible') as live:
                    # Pass the Graphiti client as a dependency
                    deps = GraphitiDependencies(graphiti_client=graphiti_client)
                    
                    async with graphiti_agent.run_a_stream(
                        user_input, message_history=messages, deps=deps
                    ) as result:
                        curr_message = ""
                        async for message in result.stream_text(delta=True):
                            curr_message += message
                            live.update(Markdown(curr_message))
                    
                    # Add the new messages to the chat history
                    messages.extend(result.all_messages())
                
            except Exception as e:
                print(f"\n[Error] An error occurred: {str(e)}")
    finally:
        # Close the Graphiti connection when done
        await graphiti_client.close()
        print("\nGraphiti connection closed.")

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("\nProgram terminated by user.")
    except Exception as e:
        print(f"\nUnexpected error: {str(e)}")
        raise

```

--------------------------------------------------------------------------------
/neo4j/docker-neo4j/src/test/java/com/neo4j/docker/TestDeprecationWarning.java:
--------------------------------------------------------------------------------

```java
package com.neo4j.docker;

import com.neo4j.docker.utils.TestSettings;
import com.neo4j.docker.utils.WaitStrategies;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.output.OutputFrame;
import org.testcontainers.containers.output.Slf4jLogConsumer;

import java.time.Duration;

public class TestDeprecationWarning
{
    private final Logger log = LoggerFactory.getLogger( TestDeprecationWarning.class );
    private static final String DEPRECATION_WARN_STRING = "Neo4j Red Hat UBI8 images are deprecated in favour of Red Hat UBI9";
    private static final String DEPRECATION_WARN_SUPPRESS_FLAG = "NEO4J_DEPRECATION_WARNING";

    // The opposite test to make sure there are no deprecation warnings in non-ubi8 images already exists at
    // com.neo4j.docker.coredb.TestBasic.testNoUnexpectedErrors
    @Test
    void shouldWarnIfUsingDeprecatedBaseOS_coreDB() throws Exception
    {
        Assumptions.assumeTrue( TestSettings.BASE_OS == TestSettings.BaseOS.UBI8,
                                "Deprecation warning should only exist in UBI8 images");
        try(GenericContainer container = new GenericContainer(TestSettings.IMAGE_ID))
        {
            container.withEnv( "NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes" )
                     .withExposedPorts( 7474, 7687 )
                     .withLogConsumer( new Slf4jLogConsumer( log ))
                     .waitingFor( WaitStrategies.waitForBoltReady() );
            container.start();
            // container should successfully start
            String logs = container.getLogs( OutputFrame.OutputType.STDERR );
            Assertions.assertTrue( logs.contains( DEPRECATION_WARN_STRING ),
                                   "Container did not warn about ubi8 deprecation. Actual error logs:\n"+logs);
        }
    }

    @Test
    void shouldWarnIfUsingDeprecatedBaseOS_admin()
    {
        Assumptions.assumeTrue( TestSettings.BASE_OS == TestSettings.BaseOS.UBI8,
                                "Deprecation warning should only exist in UBI8 images");
        try(GenericContainer container = new GenericContainer(TestSettings.ADMIN_IMAGE_ID))
        {
            container.withEnv( "NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes" )
                     .withExposedPorts( 7474, 7687 )
                     .withLogConsumer( new Slf4jLogConsumer( log ))
                     .withCommand( "neo4j-admin", "--help" );
            WaitStrategies.waitUntilContainerFinished( container, Duration.ofSeconds( 30 ));
            container.start();
            // container should successfully start
            String logs = container.getLogs( OutputFrame.OutputType.STDERR );
            Assertions.assertTrue( logs.contains( DEPRECATION_WARN_STRING ),
                                   "Container did not warn about ubi8 deprecation. Actual error logs:\n"+logs);
        }
    }

    // this is a requirement for docker official images, so doesn't need testing for neo4j-admin
    @Test
    void shouldOnlyWarnWhenRunningNeo4jCommands() throws Exception
    {
        Assumptions.assumeTrue( TestSettings.BASE_OS == TestSettings.BaseOS.UBI8,
                                "Deprecation warning should only exist in UBI8 images");
        try(GenericContainer container = new GenericContainer(TestSettings.IMAGE_ID))
        {
            container.withEnv( "NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes" )
                     .withExposedPorts( 7474, 7687 )
                     .withLogConsumer( new Slf4jLogConsumer( log ))
                     .withCommand( "cat", "/etc/os-release" );
            WaitStrategies.waitUntilContainerFinished( container, Duration.ofSeconds( 30 ) );
            container.start();
            // container should successfully start
            String logs = container.getLogs( OutputFrame.OutputType.STDERR );
            Assertions.assertFalse( logs.contains( DEPRECATION_WARN_STRING ),
                                   "Container should not have warned about ubi8 deprecation. Actual error logs:\n"+logs);
        }
    }

    @Test
    void shouldIgnoreDeprecationSuppression_coreDB() throws Exception
    {
        Assumptions.assumeTrue( TestSettings.BASE_OS == TestSettings.BaseOS.UBI8,
                                "Deprecation warning should only exist in UBI8 images");
        try(GenericContainer container = new GenericContainer(TestSettings.IMAGE_ID))
        {
            container.withEnv( "NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes" )
                     .withEnv( DEPRECATION_WARN_SUPPRESS_FLAG, "suppress" )
                     .withExposedPorts( 7474, 7687 )
                     .withLogConsumer( new Slf4jLogConsumer( log ))
                     .waitingFor( WaitStrategies.waitForBoltReady() );
            container.start();
            // container should successfully start
            String logs = container.getLogs( OutputFrame.OutputType.STDERR );
            Assertions.assertTrue( logs.contains( DEPRECATION_WARN_STRING ),
                    "Container did not warn about ubi8 deprecation. Actual error logs:\n"+logs);
        }
    }

    @Test
    void shouldIgnoreDeprecationSuppressed_admin()
    {
        Assumptions.assumeTrue( TestSettings.BASE_OS == TestSettings.BaseOS.UBI8,
                                "Deprecation warning should only exist in UBI8 images");
        try(GenericContainer container = new GenericContainer(TestSettings.ADMIN_IMAGE_ID))
        {
            container.withEnv( "NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes" )
                     .withEnv( DEPRECATION_WARN_SUPPRESS_FLAG, "suppress" )
                     .withExposedPorts( 7474, 7687 )
                     .withLogConsumer( new Slf4jLogConsumer( log ))
                     .withCommand( "neo4j-admin", "--help" );
            WaitStrategies.waitUntilContainerFinished( container, Duration.ofSeconds( 30 ));
            container.start();
            // container should successfully start
            String logs = container.getLogs( OutputFrame.OutputType.STDERR );
            Assertions.assertTrue( logs.contains( DEPRECATION_WARN_STRING ),
                    "Container did not warn about ubi8 deprecation. Actual error logs:\n"+logs);
        }
    }
}

```

--------------------------------------------------------------------------------
/neo4j/docker-neo4j/src/test/java/com/neo4j/docker/neo4jadmin/TestBackupRestore44.java:
--------------------------------------------------------------------------------

```java
package com.neo4j.docker.neo4jadmin;

import com.neo4j.docker.coredb.configurations.Configuration;
import com.neo4j.docker.coredb.configurations.Setting;
import com.neo4j.docker.utils.DatabaseIO;
import com.neo4j.docker.utils.Neo4jVersion;
import com.neo4j.docker.utils.SetContainerUser;
import com.neo4j.docker.utils.WaitStrategies;
import com.neo4j.docker.utils.TemporaryFolderManager;
import com.neo4j.docker.utils.TestSettings;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;

import java.nio.file.Path;
import java.time.Duration;
import java.util.Map;

public class TestBackupRestore44
{
    // with authentication
    // with non-default user
    private final Logger log = LoggerFactory.getLogger( TestBackupRestore44.class );
    @RegisterExtension
    public static TemporaryFolderManager temporaryFolderManager = new TemporaryFolderManager();

    @BeforeAll
    static void beforeAll()
    {
        Assumptions.assumeTrue( TestSettings.NEO4J_VERSION.isAtLeastVersion( Neo4jVersion.NEO4J_VERSION_440),
                                "Neo4j admin image not available before 4.4.0");
        Assumptions.assumeTrue( TestSettings.NEO4J_VERSION.isOlderThan( Neo4jVersion.NEO4J_VERSION_500 ),
                                "These Neo4j admin tests are only for 4.4");
        Assumptions.assumeTrue( TestSettings.EDITION == TestSettings.Edition.ENTERPRISE,
                                "backup and restore only available in Neo4j Enterprise" );
    }

    private GenericContainer createDBContainer( boolean asDefaultUser, String password )
    {
        String auth = "none";
        if(!password.equalsIgnoreCase("none"))
        {
            auth = "neo4j/"+password;
        }
        Map<Setting,Configuration> confNames = Configuration.getConfigurationNameMap();
        GenericContainer container = new GenericContainer( TestSettings.IMAGE_ID );
        container.withEnv( "NEO4J_AUTH", auth )
                 .withEnv( "NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes" )
                 .withEnv( confNames.get( Setting.BACKUP_ENABLED ).envName, "true" )
                 .withEnv( confNames.get( Setting.BACKUP_LISTEN_ADDRESS ).envName, "0.0.0.0:6362" )
                 .withExposedPorts( 7474, 7687, 6362 )
                 .withLogConsumer( new Slf4jLogConsumer( log ) )
                 .waitingFor(WaitStrategies.waitForNeo4jReady( password ));
        if(!asDefaultUser)
        {
            SetContainerUser.nonRootUser( container );
        }
        return container;
    }

    private GenericContainer createAdminContainer( boolean asDefaultUser )
    {
        GenericContainer container = new GenericContainer( TestSettings.ADMIN_IMAGE_ID );
        container.withEnv( "NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes" )
                 .withLogConsumer( new Slf4jLogConsumer( log ) );
        WaitStrategies.waitUntilContainerFinished( container, Duration.ofSeconds( 180) );
        if(!asDefaultUser)
        {
            SetContainerUser.nonRootUser( container );
        }
        return container;
    }

    @Test
    void shouldBackupAndRestore_defaultUser_noAuth() throws Exception
    {
        testCanBackupAndRestore( true, "none" );
    }
    @Test
    void shouldBackupAndRestore_nonDefaultUser_noAuth() throws Exception
    {
        testCanBackupAndRestore( false, "none" );
    }
    @Test
    void shouldBackupAndRestore_defaultUser_withAuth() throws Exception
    {
        testCanBackupAndRestore( true, "secretpassword" );
    }
    @Test
    void shouldBackupAndRestore_nonDefaultUser_withAuth() throws Exception
    {
        testCanBackupAndRestore( false, "secretpassword" );
    }

    private void testCanBackupAndRestore(boolean asDefaultUser, String password) throws Exception
    {
        final String dbUser = "neo4j";

        // BACKUP
        // start a database and populate data
        GenericContainer neo4j = createDBContainer( asDefaultUser, password );
        Path dataDir = temporaryFolderManager.createFolderAndMountAsVolume(neo4j, "/data");
        neo4j.start();
        DatabaseIO dbio = new DatabaseIO( neo4j );
        dbio.putInitialDataIntoContainer( dbUser, password );
        dbio.verifyInitialDataInContainer( dbUser, password );

        // start admin container to initiate backup
        String neoDBAddress = neo4j.getHost()+":"+neo4j.getMappedPort( 6362 );
        GenericContainer adminBackup = createAdminContainer( asDefaultUser )
                .withNetworkMode( "host" )
                .waitingFor( new LogMessageWaitStrategy().withRegEx( "^Backup complete successful.*" ) )
                .withCommand( "neo4j-admin", "backup", "--database=neo4j", "--backup-dir=/backups", "--from="+neoDBAddress);

        Path backupDir = temporaryFolderManager.createFolderAndMountAsVolume(adminBackup, "/backups");
        adminBackup.start();

        Assertions.assertTrue( neo4j.isRunning(), "neo4j container should still be running" );
        dbio.verifyInitialDataInContainer( dbUser, password );
        adminBackup.stop();

        // RESTORE

        // write more stuff
        dbio.putMoreDataIntoContainer( dbUser, password );
        dbio.verifyMoreDataIntoContainer( dbUser, password, true );

        // do restore
        dbio.runCypherQuery( dbUser, password, "STOP DATABASE neo4j", "system" );
        GenericContainer adminRestore = createAdminContainer( asDefaultUser )
                .waitingFor( new LogMessageWaitStrategy().withRegEx( "^.*restoreStatus=successful.*" ) )
                .withCommand( "neo4j-admin", "restore", "--database=neo4j", "--from=/backups/neo4j", "--force");
        temporaryFolderManager.mountHostFolderAsVolume( adminRestore, backupDir, "/backups" );
        temporaryFolderManager.mountHostFolderAsVolume( adminRestore, dataDir, "/data" );
        adminRestore.start();
        dbio.runCypherQuery( dbUser, password, "START DATABASE neo4j", "system" );

        // verify new stuff is missing
        dbio.verifyMoreDataIntoContainer( dbUser, password, false );

        // clean up
        adminRestore.stop();
        neo4j.stop();
    }
}

```

--------------------------------------------------------------------------------
/neo4j/docker-neo4j/src/test/java/com/neo4j/docker/utils/DatabaseIO.java:
--------------------------------------------------------------------------------

```java
package com.neo4j.docker.utils;

import com.neo4j.docker.coredb.configurations.Configuration;
import org.junit.jupiter.api.Assertions;
import org.neo4j.driver.AuthToken;
import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Config;
import org.neo4j.driver.Driver;
import org.neo4j.driver.GraphDatabase;
import org.neo4j.driver.Record;
import org.neo4j.driver.Result;
import org.neo4j.driver.Session;
import org.neo4j.driver.SessionConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.GenericContainer;

import java.util.List;
import java.util.stream.Collectors;

public class DatabaseIO
{
	private static final Config DEFAULT_DRIVER_CONFIG = Config.builder().withoutEncryption().build();
	private final Logger log = LoggerFactory.getLogger( DatabaseIO.class );

	private GenericContainer container;
	private String boltUri;

	public DatabaseIO( GenericContainer container )
	{
		this.container = container;
        this.boltUri = "bolt://"+container.getHost()+":"+container.getMappedPort( 7687 );
	}

    public DatabaseIO( String host, Integer boltPort )
    {
        this.boltUri = "bolt://" + host + ":" + boltPort;
    }

	public void putInitialDataIntoContainer( String user, String password )
	{
		log.info( "Writing data into database" );
        List<Record> result = runCypherQuery( user, password,"CREATE (arne:dog {name:'Arne'})-[:SNIFFS]->(bosse:dog {name:'Bosse'}) RETURN arne.name" );
        Assertions.assertEquals( "Arne", result.get( 0 ).get( "arne.name" ).asString(), "did not receive expected result from cypher CREATE query" );
	}

	public void verifyInitialDataInContainer( String user, String password )
	{
		log.info( "verifying data is present in the database" );		
		List<Record> result = runCypherQuery( user, password,"MATCH (a:dog)-[:SNIFFS]->(b:dog) RETURN a.name");
        Assertions.assertEquals( "Arne", result.get( 0 ).get("a.name").asString(), "did not receive expected result from cypher MATCH query" );
	}

	public void putMoreDataIntoContainer( String user, String password )
	{
		log.info( "Writing more data into database" );
        List<Record> result = runCypherQuery( user, password,
                      "MATCH (a:dog {name:'Arne'}) CREATE (armstrong:dog {name:'Armstrong'})-[:SNIFFS]->(a) return a.name, armstrong.name" );
        Assertions.assertEquals( "Arne", result.get( 0 ).get("a.name").asString(),
                                 "did not receive expected result from cypher MATCH query" );
        Assertions.assertEquals( "Armstrong", result.get( 0 ).get( "armstrong.name" ).asString(),
                                 "did not receive expected result from cypher CREATE query" );
	}

	public void verifyMoreDataIntoContainer( String user, String password, boolean extraDataShouldBeThere )
	{
		log.info( "Verifying extra data is {}in database", extraDataShouldBeThere? "":"not " );
		List<Record> result = runCypherQuery( user, password,"MATCH (a:dog)-[:SNIFFS]->(b:dog) RETURN a.name");
		String dogs = result.stream()
                            .map( record -> record.get( 0 ).asString() )
                            .sorted()
                            .collect( Collectors.joining(","));
		// dogs should now be a String which is a comma delimited list of dog names

		if(extraDataShouldBeThere)
        {
            Assertions.assertEquals( "Armstrong,Arne", dogs, "cypher query did not return correct data" );
        }
		else
        {
            Assertions.assertEquals( "Arne", dogs, "cypher query did not return correct data" );
        }
	}

    public String getConfigurationSettingAsString( String user, String password, Configuration conf)
    {
        List<Record> confRecord = runCypherQuery( user, password,
                                                  "CALL dbms.listConfig() YIELD name, value " +
                                                  "WHERE name='" + conf.name + "' " +
                                                  "RETURN value" );
        Assertions.assertEquals(1, confRecord.size(), "Configuration "+conf.name+" was not set." );
        return confRecord.get( 0 ).get( 0 ).asString();
    }

    public void verifyConfigurationSetting( String user, String password, Configuration conf, String expectedValue)
    {
        verifyConfigurationSetting(user, password, conf, expectedValue, "");
    }

    public void verifyConfigurationSetting( String user, String password, Configuration conf, String expectedValue, String extraFailureMsg)
    {
        String actualConf = getConfigurationSettingAsString( user, password, conf );
        Assertions.assertEquals(expectedValue, actualConf,
                                String.format("Expected %s to be %s but it was %s.%s",
                                              conf.name, expectedValue, actualConf, extraFailureMsg));
    }

	public void changePassword(String user, String oldPassword, String newPassword)
	{
		if(TestSettings.NEO4J_VERSION.isAtLeastVersion( Neo4jVersion.NEO4J_VERSION_400 ))
		{
		    String cypher = "ALTER CURRENT USER SET PASSWORD FROM '"+oldPassword+"' TO '"+newPassword+"'";
		    runCypherQuery( user, oldPassword, cypher, "system" );
		}
		else
		{
		    runCypherQuery( user, oldPassword, "CALL dbms.changePassword('"+newPassword+"')" );
		}
	}

	public List<Record> runCypherQuery( String user, String password, String cypher)
    {
        // we don't just do runCypherQuery( user, password, cypher, "neo4j")
        // because it breaks the upgrade tests from 3.5.x
        List<Record> records;
		Driver driver = GraphDatabase.driver( boltUri, getToken( user, password ), DEFAULT_DRIVER_CONFIG);
		try ( Session session = driver.session())
		{
			Result rs = session.run( cypher );
			records = rs.list();
		}
		driver.close();
		return records;
    }

	public List<Record> runCypherQuery( String user, String password, String cypher, String database)
    {
        List<Record> records;
		Driver driver = GraphDatabase.driver( boltUri, getToken( user, password ), DEFAULT_DRIVER_CONFIG);
		try ( Session session = driver.session(SessionConfig.forDatabase( database )))
		{
			Result rs = session.run( cypher );
			records = rs.list();
		}
		driver.close();
		return records;
    }

	public void verifyConnectivity( String user, String password )
	{
		GraphDatabase.driver( boltUri,
							  getToken( user, password ),
						DEFAULT_DRIVER_CONFIG)
					 .verifyConnectivity();
	}

	private AuthToken getToken(String user, String password)
	{
		if(password.equals( "none" ))
		{
			return AuthTokens.none();
		}
		else
		{
			return AuthTokens.basic( user, password );
		}
	}
}

```

--------------------------------------------------------------------------------
/neo4j/docker-neo4j/src/test/java/com/neo4j/docker/coredb/configurations/TestJVMAdditionalConfig.java:
--------------------------------------------------------------------------------

```java
package com.neo4j.docker.coredb.configurations;

import com.neo4j.docker.utils.DatabaseIO;
import com.neo4j.docker.utils.Neo4jVersion;
import com.neo4j.docker.utils.SetContainerUser;
import com.neo4j.docker.utils.TemporaryFolderManager;
import com.neo4j.docker.utils.TestSettings;
import com.neo4j.docker.utils.WaitStrategies;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.output.Slf4jLogConsumer;

import java.nio.file.Files;
import java.nio.file.Path;

public class TestJVMAdditionalConfig
{
    private final Logger log = LoggerFactory.getLogger( TestJVMAdditionalConfig.class );
    private static final String PASSWORD = "SuperSecretPassword";
    private static final String AUTH = "neo4j/"+PASSWORD ;
    private static Path confFolder;
    private static final Configuration JVM_ADDITIONAL_CONFIG = Configuration.getConfigurationNameMap().get( Setting.JVM_ADDITIONAL );
    private static final String DEFAULT_JVM_CONF = "-XX:+UseG1GC";

    @RegisterExtension
    public static TemporaryFolderManager temporaryFolderManager = new TemporaryFolderManager();

    @BeforeAll
    static void getVersionSpecificConfigurationSettings()
    {
        confFolder = Configuration.getConfigurationResourcesFolder();
        Assumptions.assumeTrue( TestSettings.NEO4J_VERSION.isAtLeastVersion( Neo4jVersion.NEO4J_VERSION_440 ),
                                "JVM Additional tests not applicable before 4.4.0");
    }

    private GenericContainer createContainer()
    {
        return new GenericContainer(TestSettings.IMAGE_ID)
                .withEnv("NEO4J_AUTH", AUTH)
                .withEnv("NEO4J_DEBUG", AUTH)
                .withEnv("NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes")
                .withExposedPorts(7474, 7687)
                .withLogConsumer(new Slf4jLogConsumer( log))
                .waitingFor(WaitStrategies.waitForNeo4jReady(PASSWORD));
    }

    @Test
    void testJvmAdditionalNotOverridden_noEnv() throws Exception
    {
        String expectedJvmAdditional = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005";
        testJvmAdditionalNotOverridden(expectedJvmAdditional, "" );
    }

    @Test
    void testJvmAdditionalNotOverridden_withEnv() throws Exception
    {
        String jvmAdditionalFromEnv = "-XX:+HeapDumpOnOutOfMemoryError";
        String expectedJvmAdditional = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005\n"+jvmAdditionalFromEnv;
        testJvmAdditionalNotOverridden(expectedJvmAdditional, jvmAdditionalFromEnv );
    }

    void testJvmAdditionalNotOverridden( String expectedJvmAdditional, String jvmAdditionalEnv ) throws Exception
    {
        try( GenericContainer container = createContainer())
        {
            //Mount /conf
            Path confMount = temporaryFolderManager.createFolderAndMountAsVolume(container, "/conf");
            SetContainerUser.nonRootUser( container );
            container.withEnv( JVM_ADDITIONAL_CONFIG.envName, jvmAdditionalEnv );
            //Create JvmAdditionalNotOverridden.conf file
            Path confFile = confFolder.resolve( "JvmAdditionalNotOverridden.conf" );
            Files.copy( confFile, confMount.resolve( "neo4j.conf" ) );
            //Start the container
            container.start();
            // verify setting correctly loaded into neo4j
            DatabaseIO dbio = new DatabaseIO( container );
            dbio.verifyConfigurationSetting( "neo4j", PASSWORD, JVM_ADDITIONAL_CONFIG, expectedJvmAdditional);
        }
    }

    @Test
    void testJVMAdditionalDefaultsNotOverwrittenByEnv() throws Exception
    {
        String expectedJvmAdditional = "-XX:+HeapDumpOnOutOfMemoryError";
        try( GenericContainer container = createContainer())
        {
            container.withEnv( JVM_ADDITIONAL_CONFIG.envName, expectedJvmAdditional );
            verifyJvmAdditional( container, expectedJvmAdditional, DEFAULT_JVM_CONF );
        }
    }

    @Test
    void testSpecialCharInJvmAdditional_space_conf() throws Exception
    {
        testJvmAdditionalSpecialCharacters_conf("space", "-XX:OnOutOfMemoryError=\"/usr/bin/echo oh no oom\"");
    }

    @Test
    void testSpecialCharInJvmAdditional_space_env() throws Exception
    {
        testJvmAdditionalSpecialCharacters_env( "-XX:OnOutOfMemoryError=\"/usr/bin/echo oh no oom\"");
    }

    @Test
    void testSpecialCharInJvmAdditional_dollar_conf() throws Exception
    {
        testJvmAdditionalSpecialCharacters_conf("dollar",
                                                "-Dnot.a.real.parameter=\"beepbeep$boop1boop2\"" );
    }

    @Test
    void testSpecialCharInJvmAdditional_dollar_env() throws Exception {
        testJvmAdditionalSpecialCharacters_env( "-Dnot.a.real.parameter=\"bleepblorp$bleep1blorp4\"");
    }

    void testJvmAdditionalSpecialCharacters_conf(String charName, String expectedJvmAdditional) throws Exception
    {
        try(GenericContainer container = createContainer())
        {
            //Mount /conf
            Path confMount = temporaryFolderManager.createFolderAndMountAsVolume(container, "/conf");
            //copy test conf file
            String confContent = JVM_ADDITIONAL_CONFIG.name + "=" + expectedJvmAdditional;
            Files.write( confMount.resolve( "neo4j.conf" ), confContent.getBytes() );
            //Start the container
            verifyJvmAdditional( container, expectedJvmAdditional );
        }
    }

    void testJvmAdditionalSpecialCharacters_env( String expectedJvmAdditional ) throws Exception
    {
        try(GenericContainer container = createContainer())
        {
            container.withEnv( JVM_ADDITIONAL_CONFIG.envName, expectedJvmAdditional);
            verifyJvmAdditional( container, expectedJvmAdditional, DEFAULT_JVM_CONF );
        }
    }

    void verifyJvmAdditional( GenericContainer container, String... expectedValues )
    {
        SetContainerUser.nonRootUser( container );
        //Start the container
        container.start();
        // verify setting correctly loaded into neo4j
        DatabaseIO dbio = new DatabaseIO( container );
        String actualConfValue = dbio.getConfigurationSettingAsString( "neo4j", PASSWORD, JVM_ADDITIONAL_CONFIG );
        
        for(String expectedJvmAdditional : expectedValues)
        {
            Assertions.assertTrue( actualConfValue.contains( expectedJvmAdditional ) );
        }
    }
}

```

--------------------------------------------------------------------------------
/neo4j/docker-neo4j/src/test/java/com/neo4j/docker/coredb/plugins/StubPluginHelper.java:
--------------------------------------------------------------------------------

```java
package com.neo4j.docker.coredb.plugins;

import com.google.gson.Gson;
import com.neo4j.docker.utils.DatabaseIO;
import com.neo4j.docker.utils.HostFileHttpHandler;
import com.neo4j.docker.utils.HttpServerTestExtension;
import com.neo4j.docker.utils.Neo4jVersion;
import org.junit.jupiter.api.Assertions;
import org.neo4j.driver.Record;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.neo4j.docker.utils.TestSettings.NEO4J_VERSION;

public class StubPluginHelper
{
    public static final String PLUGIN_FILENAME = "myPlugin.jar";
    public static final String PLUGIN_ENV_NAME = "_testing";
    private final HttpServerTestExtension httpServer;

    /**Data class for each <code>versions.json</code> entry so that the GSON tool can convert it to json.
     * */
    private class VersionsJsonEntry
    {
        String neo4j;
        String jar;
        String _testing;

        VersionsJsonEntry( String neo4j, String jar )
        {
            this.neo4j = neo4j;
            this._testing = "SNAPSHOT";
            this.jar = "http://host.testcontainers.internal:3000/" + jar;
        }
    }

    /**Does (most of) the complicated setup required to create a fake neo4j plugin and make it accessible inside the container.
     * @param httpServer a {@link HttpServerTestExtension} object.
     *                  It must have ALREADY been registered as a JUnit5 extension with {@link org.junit.jupiter.api.extension.RegisterExtension}
     * */
    public StubPluginHelper(HttpServerTestExtension httpServer)
    {
        this.httpServer = httpServer;
    }

    /**Creates a versions.json in the destination folder mapping between the given neo4j versions and jar filenames.
     * This is currently used to map between neo4j versions and jars that don't exist (for semver match testing).
     * @param destinationFolder folder to save versions.json to.
     * @param version a neo4j version, to map to the real testing jar.
     * @return File object of the versions.json file created.
     * */
    public File createStubPluginForVersion(Path destinationFolder, Neo4jVersion version) throws IOException
    {
        return createStubPluginsForVersionMapping(destinationFolder, Collections.singletonMap(version.toString(), PLUGIN_FILENAME));
    }

    /**Creates a versions.json in the destination folder mapping between the given neo4j versions and jar filenames.
     * This is currently used to map between neo4j versions and jars that don't exist (for semver match testing).
     * @param destinationFolder folder to save versions.json to.
     * @param version a neo4j version, as a string, to map to the real testing jar.
     * @return File object of the versions.json file created.
     * */
    public File createStubPluginForVersion(Path destinationFolder, String version) throws IOException
    {
        return createStubPluginsForVersionMapping(destinationFolder, Collections.singletonMap(version, PLUGIN_FILENAME));
    }

    /**Creates a versions.json in the destination folder mapping between the given neo4j versions and jar filenames.
     * This is currently used to map between neo4j versions and jars that don't exist (for semver match testing).
     * @param destinationFolder folder to save versions.json to.
     * @param versionAndJar map of neo4j version, as a string, to jar name. For example:
     *                      4.4.10 -> myPlugin.jar
     *                      4.4.*  -> pluginThatDoesNotExist.jar
     *                      5.0.x  -> anotherNonexistantPlugin.jar
     * @return File object of the versions.json file created.
     * */
    public File createStubPluginsForVersionMapping(Path destinationFolder, Map<String,String> versionAndJar ) throws IOException
    {
        File versionsJson = createVersionsJson(destinationFolder, versionAndJar);
        try {
            File myPluginJar = new File(getClass().getClassLoader().getResource("stubplugin/" + PLUGIN_FILENAME).toURI());

            httpServer.registerHandler(versionsJson.getName(), new HostFileHttpHandler(versionsJson, "application/json"));
            httpServer.registerHandler(PLUGIN_FILENAME, new HostFileHttpHandler(myPluginJar, "application/java-archive"));
        }
        catch (URISyntaxException e)
        {
            throw new IOException("Could not load test plugin from test resources file", e);
        }
        return versionsJson;
    }

    private File createVersionsJson(Path destinationFolder, Map<String, String> versionAndJar) throws IOException
    {
        List<VersionsJsonEntry> jsonEntries = versionAndJar.keySet()
                .stream()
                .map(key -> new VersionsJsonEntry(key, versionAndJar.get(key)))
                .collect(Collectors.toList());
        Gson jsonBuilder = new Gson();
        String jsonStr = jsonBuilder.toJson(jsonEntries);

        File outputJsonFile = destinationFolder.resolve("versions.json").toFile();
        java.nio.file.Files.writeString(outputJsonFile.toPath(), jsonStr);
        return outputJsonFile;
    }

    public void verifyStubPluginLoaded(DatabaseIO db, String user, String password )
    {
        // when we check the list of installed procedures...
        String listProceduresCypherQuery = NEO4J_VERSION.isAtLeastVersion( new Neo4jVersion( 4, 3, 0 ) ) ?
                                           "SHOW PROCEDURES YIELD name, signature RETURN name, signature" :
                                           "CALL dbms.procedures() YIELD name, signature RETURN name, signature";
        List<Record> procedures = db.runCypherQuery( user, password, listProceduresCypherQuery );
        // Then the procedure from the test plugin should be listed
        Assertions.assertTrue( procedures.stream()
                                         .anyMatch( x -> x.get( "name" ).asString()
                                                          .equals( "com.neo4j.docker.test.myplugin.defaultValues" ) ),
                               "Missing procedure provided by our plugin" );

        // When we call the procedure from the plugin
        List<Record> pluginResponse = db.runCypherQuery( user, password,
                                                         "CALL com.neo4j.docker.test.myplugin.defaultValues" );

        // Then we get the response we expect
        Assertions.assertEquals( 1, pluginResponse.size(), "Our procedure should only return a single result" );
        Record record = pluginResponse.get( 0 );

        String message = "Result from calling our procedure doesnt match our expectations";
        Assertions.assertEquals( "a string", record.get( "string" ).asString(), message );
        Assertions.assertEquals( 42L, record.get( "integer" ).asInt(), message );
        Assertions.assertEquals( 3.14d, record.get( "aFloat" ).asDouble(), 0.000001, message );
        Assertions.assertTrue(record.get("aBoolean").asBoolean(), message);
    }
}

```

--------------------------------------------------------------------------------
/neo4j/docker-neo4j/src/test/java/com/neo4j/docker/neo4jadmin/TestDumpLoad44.java:
--------------------------------------------------------------------------------

```java
package com.neo4j.docker.neo4jadmin;

import com.github.dockerjava.api.command.CreateContainerCmd;
import com.neo4j.docker.utils.DatabaseIO;
import com.neo4j.docker.utils.Neo4jVersion;
import com.neo4j.docker.utils.SetContainerUser;
import com.neo4j.docker.utils.WaitStrategies;
import com.neo4j.docker.utils.TemporaryFolderManager;
import com.neo4j.docker.utils.TestSettings;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;
import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;

import java.nio.file.Path;
import java.time.Duration;
import java.util.function.Consumer;

public class TestDumpLoad44
{
    private static Logger log = LoggerFactory.getLogger( TestDumpLoad44.class );
    @RegisterExtension
    public static TemporaryFolderManager temporaryFolderManager = new TemporaryFolderManager();

    @BeforeAll
    static void beforeAll()
    {
        Assumptions.assumeTrue( TestSettings.NEO4J_VERSION.isAtLeastVersion( new Neo4jVersion( 4, 4, 0 )),
                                "Neo4j admin image not available before 4.4.0");
        Assumptions.assumeTrue( TestSettings.NEO4J_VERSION.isOlderThan( Neo4jVersion.NEO4J_VERSION_500 ),
                                "These Neo4j admin tests are only for 4.4");
    }

    private GenericContainer createDBContainer( boolean asDefaultUser, String password )
    {
        String auth = "none";
        if(!password.equalsIgnoreCase("none"))
        {
            auth = "neo4j/"+password;
        }

        GenericContainer container = new GenericContainer( TestSettings.IMAGE_ID );
        container.withEnv( "NEO4J_AUTH", auth )
                 .withEnv( "NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes" )
                 .withExposedPorts( 7474, 7687 )
                 .withLogConsumer( new Slf4jLogConsumer( log ) )
                 .waitingFor( WaitStrategies.waitForNeo4jReady( password ) )
                 // the default testcontainer framework behaviour is to just stop the process entirely,
                 // preventing clean shutdown. This means we can run the stop command and
                 // it'll send a SIGTERM to initiate neo4j shutdown. See also stopContainer method.
                 .withCreateContainerCmdModifier(
                         (Consumer<CreateContainerCmd>) cmd -> cmd.withStopSignal( "SIGTERM" ).withStopTimeout( 20 ));
        if(!asDefaultUser)
        {
            SetContainerUser.nonRootUser( container );
        }
        return container;
    }

    private GenericContainer createAdminContainer( boolean asDefaultUser )
    {
        GenericContainer container = new GenericContainer( TestSettings.ADMIN_IMAGE_ID );
        container.withEnv( "NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes" )
                 .withExposedPorts( 7474, 7687 )
                 .withLogConsumer( new Slf4jLogConsumer( log ) )
                 .waitingFor( new LogMessageWaitStrategy().withRegEx( "^Done: \\d+ files, [\\d\\.,]+[KMGi]*B processed.*" ) )
                 .withStartupCheckStrategy( new OneShotStartupCheckStrategy().withTimeout( Duration.ofSeconds( 90 ) ) );
        if(!asDefaultUser)
        {
            SetContainerUser.nonRootUser( container );
        }
        return container;
    }

    @Test
    void shouldDumpAndLoad_defaultUser_noAuth() throws Exception
    {
        shouldCreateDumpAndLoadDump( true, "none" );
    }

    @Test
    void shouldDumpAndLoad_nonDefaultUser_noAuth() throws Exception
    {
        shouldCreateDumpAndLoadDump( false, "none" );
    }

    @Test
    void shouldDumpAndLoad_defaultUser_withAuth() throws Exception
    {
        shouldCreateDumpAndLoadDump( true, "verysecretpassword" );
    }

    @Test
    void shouldDumpAndLoad_nonDefaultUser_withAuth() throws Exception
    {
        shouldCreateDumpAndLoadDump( false, "verysecretpassword" );
    }

    //container.stop() actually runs the killContainer Command, preventing clean shutdown.
    // This runs the actual stop command. Which we set up in createDBContainer to send SIGTERM
    private void stopContainer(GenericContainer container)
    {
        log.info( "issuing container stop command" );
        container.getDockerClient().stopContainerCmd( container.getContainerId() ).exec();
        log.info( "Container stopped" );
    }

    private void shouldCreateDumpAndLoadDump( boolean asDefaultUser, String password ) throws Exception
    {
        Path firstDataDir;
        Path secondDataDir;
        Path backupDir;

        // start a database and populate it
        try(GenericContainer container = createDBContainer( asDefaultUser, password ))
        {
            firstDataDir = temporaryFolderManager.createNamedFolderAndMountAsVolume( container,
                                                                                     "data1",
                                                                                     "/data" );
            container.start();
            DatabaseIO dbio = new DatabaseIO( container );
            dbio.putInitialDataIntoContainer( "neo4j", password );
            stopContainer( container );
        }

        // use admin container to create dump
        try(GenericContainer admin = createAdminContainer( asDefaultUser ))
        {
            temporaryFolderManager.mountHostFolderAsVolume( admin, firstDataDir, "/data" );
            backupDir = temporaryFolderManager.createFolderAndMountAsVolume(admin, "/backups");
            admin.withCommand( "neo4j-admin", "dump", "--database=neo4j", "--to=/backups/neo4j.dump" );
            admin.start();
        }
        Assertions.assertTrue( backupDir.resolve( "neo4j.dump" ).toFile().exists(), "dump file not created");

        // dump file exists. Now try to load it into a new database.
        // use admin container to create dump
        try(GenericContainer admin = createAdminContainer( asDefaultUser ))
        {
            secondDataDir = temporaryFolderManager.createNamedFolderAndMountAsVolume( admin,
                                                                                     "data2",
                                                                                     "/data" );
            temporaryFolderManager.mountHostFolderAsVolume( admin, backupDir, "/backups" );
            admin.withCommand( "neo4j-admin", "load", "--database=neo4j", "--from=/backups/neo4j.dump" );
            admin.start();
        }

        // verify data in 2nd data directory by starting a database and verifying data we populated earlier
        try(GenericContainer container = createDBContainer( asDefaultUser, password ))
        {
            temporaryFolderManager.mountHostFolderAsVolume( container, secondDataDir, "/data" );
            container.start();
            DatabaseIO dbio = new DatabaseIO( container );
            dbio.verifyInitialDataInContainer( "neo4j", password );
        }
    }
}

```

--------------------------------------------------------------------------------
/neo4j/docker-neo4j/build-docker-image.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/bash
set -eu -o pipefail

# SUPPORTED_IMAGE_OS=("debian" "ubi9")
EDITIONS=("community" "enterprise")

DISTRIBUTION_SITE="https://dist.neo4j.org"
ROOT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$ROOT_DIR/build-utils-common-functions.sh"
BUILD_DIR=${ROOT_DIR}/build
SRC_DIR=${ROOT_DIR}/docker-image-src
# shellcheck disable=SC2034  # Used in docker-common-functions.sh
TAR_CACHE=${ROOT_DIR}/in

function usage
{
    echo >&2 "USAGE: $0 <version> <edition> <operating system>
    For example:
        $0 4.4.10 community debian
        $0 5.10.0 enterprise ubi9
    Version and operating system can also be set in the environment.
    For example:
        NEO4JVERSION=4.4.10 NEO4JEDITION=community IMAGE_OS=debian $0
        NEO4JVERSION=5.10.0 NEO4JEDITION=enterprise IMAGE_OS=ubi9 $0
    "
    exit 1
}

## ==========================================
## get and sanitise script inputs

if [[ $# -eq 3 ]]; then
    NEO4JVERSION=${1}
    NEO4JEDITION=${2}
    IMAGE_OS=${3}
elif [[ -z ${NEO4JVERSION:-""} ]]; then
    echo >&2 "NEO4JVERSION is unset. Either set it in the environment or pass as argument to this script."
    usage
elif [[ -z ${NEO4JEDITION:-""} ]]; then
    echo >&2 "NEO4JEDITION is unset. Either set it in the environment or pass as argument to this script."
    usage
elif [[ -z ${IMAGE_OS:-""} ]]; then
    echo >&2 "IMAGE_OS is unset. Either set it in the environment or pass as argument to this script."
    usage
fi
# verify edition
if ! contains_element "${NEO4JEDITION}" "${EDITIONS[@]}"; then
    echo >&2 "${NEO4JEDITION} is not a supported edition."
    usage
fi
# verify compatible neo4j version
if [[ ! "${NEO4JVERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+.*$  ]]; then
    echo "\"${NEO4JVERSION}\" is not a valid version number."
    usage
fi

# get source files
BRANCH=$(get_branch_from_version ${NEO4JVERSION})
DOCKERFILE_NAME=$(get_compatible_dockerfile_for_os_or_error "${BRANCH}" "${IMAGE_OS}")

echo "Building docker neo4j-${NEO4JEDITION}-${NEO4JVERSION} image based on ${IMAGE_OS}."

## ==================================================================================
## get neo4j source tar from distribution site. This is required for release artifacts
## so that we can calculate the sha256, and for the local image build.

echo "Caching neo4j source tarball"
fetch_tarball "${NEO4JVERSION}" "${NEO4JEDITION}"

## ==================================================================================
## construct local build context. These are all the files required to build the
## neo4j image locally.

echo "Building local context for docker build"
COREDB_LOCALCXT_DIR=${BUILD_DIR}/${IMAGE_OS}/coredb/${NEO4JEDITION}
ADMIN_LOCALCXT_DIR=${BUILD_DIR}/${IMAGE_OS}/neo4j-admin/${NEO4JEDITION}
mkdir -p ${COREDB_LOCALCXT_DIR}
mkdir -p ${ADMIN_LOCALCXT_DIR}

# copy coredb sources
mkdir -p ${COREDB_LOCALCXT_DIR}/local-package
cp ${SRC_DIR}/common/* ${COREDB_LOCALCXT_DIR}/local-package
cp ${SRC_DIR}/${BRANCH}/coredb/*.sh ${COREDB_LOCALCXT_DIR}/local-package
cp ${SRC_DIR}/${BRANCH}/coredb/*.json ${COREDB_LOCALCXT_DIR}/local-package
coredb_sha=$(sha256sum "$(cached_tarball "${NEO4JVERSION}" "${NEO4JEDITION}")" | cut -d' ' -f1)
cp "$(cached_tarball "${NEO4JVERSION}" "${NEO4JEDITION}")" ${COREDB_LOCALCXT_DIR}/local-package/

# create coredb Dockerfile
cp "${SRC_DIR}/${BRANCH}/coredb/${DOCKERFILE_NAME}" "${COREDB_LOCALCXT_DIR}/Dockerfile"
sed -i -e "s|%%NEO4J_SHA%%|${coredb_sha}|" "${COREDB_LOCALCXT_DIR}/Dockerfile"
sed -i -e "s|%%NEO4J_TARBALL%%|$(tarball_name "${NEO4JVERSION}" "${NEO4JEDITION}")|" "${COREDB_LOCALCXT_DIR}/Dockerfile"
sed -i -e "s|%%NEO4J_EDITION%%|${NEO4JEDITION}|" "${COREDB_LOCALCXT_DIR}/Dockerfile"
sed -i -e "s|%%NEO4J_DIST_SITE%%|${DISTRIBUTION_SITE}|" "${COREDB_LOCALCXT_DIR}/Dockerfile"

# copy neo4j-admin sources
mkdir -p ${ADMIN_LOCALCXT_DIR}/local-package
cp ${SRC_DIR}/common/* ${ADMIN_LOCALCXT_DIR}/local-package
cp "$(cached_tarball "${NEO4JVERSION}" "${NEO4JEDITION}")" ${ADMIN_LOCALCXT_DIR}/local-package/
cp ${SRC_DIR}/${BRANCH}/neo4j-admin/*.sh ${ADMIN_LOCALCXT_DIR}/local-package

# create neo4j-admin Dockerfile
cp "${SRC_DIR}/${BRANCH}/neo4j-admin/${DOCKERFILE_NAME}" "${ADMIN_LOCALCXT_DIR}/Dockerfile"
sed -i -e "s|%%NEO4J_SHA%%|${coredb_sha}|" "${ADMIN_LOCALCXT_DIR}/Dockerfile"
sed -i -e "s|%%NEO4J_TARBALL%%|$(tarball_name ${NEO4JVERSION} ${NEO4JEDITION})|" "${ADMIN_LOCALCXT_DIR}/Dockerfile"
sed -i -e "s|%%NEO4J_EDITION%%|${NEO4JEDITION}|" "${ADMIN_LOCALCXT_DIR}/Dockerfile"
sed -i -e "s|%%NEO4J_DIST_SITE%%|${DISTRIBUTION_SITE}|" "${ADMIN_LOCALCXT_DIR}/Dockerfile"

# add deprecation warning if needed
if [ "${IMAGE_OS}" = "ubi8" ]; then
    dep_msg="echo \>\&2 \"\n=======================================================\n
Neo4j Red Hat UBI8 images are deprecated in favour of Red Hat UBI9.\n
Update your codebase to use Neo4j Docker image tags ending with -ubi9 instead of -ubi8.\n\n
This is the last Neo4j image available on Red Hat UBI8.\n
By continuing to use UBI8 tagged Neo4j images you will not get further updates, \n
including new features and security fixes.\n\n
This message can not be suppressed.\n
=======================================================\n\"\n"
    sed -i -e "s/#%%DEPRECATION_WARNING_PLACEHOLDER%%/$(echo ${dep_msg} | sed -z 's/\n/\\n/g')/" "${COREDB_LOCALCXT_DIR}/local-package/docker-entrypoint.sh"
    sed -i -e "s/#%%DEPRECATION_WARNING_PLACEHOLDER%%/$(echo ${dep_msg} | sed -z 's/\n/\\n/g')/" "${ADMIN_LOCALCXT_DIR}/local-package/docker-entrypoint.sh"
else
    sed -i -e '/#%%DEPRECATION_WARNING_PLACEHOLDER%%/d' "${COREDB_LOCALCXT_DIR}/local-package/docker-entrypoint.sh"
    sed -i -e '/#%%DEPRECATION_WARNING_PLACEHOLDER%%/d' "${ADMIN_LOCALCXT_DIR}/local-package/docker-entrypoint.sh"
fi

## ==================================================================================
## Finally we are ready to do a docker build...

# build coredb
coredb_image_tag=neo4jtest:${RANDOM}
echo "Building CoreDB docker image for neo4j-${NEO4JVERSION} ${NEO4JEDITION} on ${IMAGE_OS}."
docker build --tag=${coredb_image_tag} \
    --build-arg="NEO4J_URI=file:///startup/$(tarball_name "${NEO4JVERSION}" "${NEO4JEDITION}")" \
    "${COREDB_LOCALCXT_DIR}"
echo "Tagged CoreDB image ${coredb_image_tag}"
echo -n "${coredb_image_tag}" > ${COREDB_LOCALCXT_DIR}/../.image-id-"${NEO4JEDITION}"

# build neo4j-admin
admin_image_tag=neo4jadmintest:${RANDOM}
echo "Building neo4j-admin docker image for neo4j-admin-${NEO4JVERSION} ${NEO4JEDITION} on ${IMAGE_OS}."
docker build --tag=${admin_image_tag} \
    --build-arg="NEO4J_URI=file:///startup/$(tarball_name "${NEO4JVERSION}" "${NEO4JEDITION}")" \
    "${ADMIN_LOCALCXT_DIR}"
echo "Tagged neo4j-admin image ${admin_image_tag}"
echo -n "${admin_image_tag}" > ${ADMIN_LOCALCXT_DIR}/../.image-id-"${NEO4JEDITION}"

## ==================================================================================
# generate env files for local development
{
    echo "NEO4JVERSION=${NEO4JVERSION}"
    echo "NEO4J_IMAGE=$(cat "${COREDB_LOCALCXT_DIR}"/../.image-id-"${NEO4JEDITION}")"
    echo "NEO4JADMIN_IMAGE=$(cat "${ADMIN_LOCALCXT_DIR}"/../.image-id-"${NEO4JEDITION}")"
    echo "NEO4J_EDITION=${NEO4JEDITION}"
    echo "BASE_OS=${IMAGE_OS}"
    echo "NEO4J_SKIP_MOUNTED_FOLDER_TARBALLING=true"
} > ${BUILD_DIR}/${IMAGE_OS}/devenv-"${NEO4JEDITION}".env
ln -f ${BUILD_DIR}/${IMAGE_OS}/devenv-"${NEO4JEDITION}".env ${BUILD_DIR}/devenv-"${NEO4JEDITION}".env


```

--------------------------------------------------------------------------------
/neo4j/docker-neo4j/src/test/java/com/neo4j/docker/neo4jadmin/TestBackupRestore.java:
--------------------------------------------------------------------------------

```java
package com.neo4j.docker.neo4jadmin;

import com.neo4j.docker.coredb.configurations.Configuration;
import com.neo4j.docker.coredb.configurations.Setting;
import com.neo4j.docker.utils.DatabaseIO;
import com.neo4j.docker.utils.Neo4jVersion;
import com.neo4j.docker.utils.SetContainerUser;
import com.neo4j.docker.utils.WaitStrategies;
import com.neo4j.docker.utils.TemporaryFolderManager;
import com.neo4j.docker.utils.TestSettings;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;
import org.testcontainers.containers.wait.strategy.Wait;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.List;
import java.util.Map;

public class TestBackupRestore
{
    // with authentication
    // with non-default user
    private final Logger log = LoggerFactory.getLogger( TestBackupRestore.class );
    @RegisterExtension
    public static TemporaryFolderManager temporaryFolderManager = new TemporaryFolderManager();

    @BeforeAll
    static void beforeAll()
    {
        Assumptions.assumeTrue( TestSettings.NEO4J_VERSION.isAtLeastVersion( Neo4jVersion.NEO4J_VERSION_500 ),
                                "These tests only apply to neo4j-admin images of 5.0 and greater");
        Assumptions.assumeTrue( TestSettings.EDITION == TestSettings.Edition.ENTERPRISE,
                                "backup and restore only available in Neo4j Enterprise" );
    }

    private GenericContainer createDBContainer( boolean asDefaultUser, String password )
    {
        String auth = "none";
        if(!password.equalsIgnoreCase("none"))
        {
            auth = "neo4j/"+password;
        }
        Map<Setting,Configuration> confNames = Configuration.getConfigurationNameMap();
        GenericContainer container = new GenericContainer( TestSettings.IMAGE_ID );
        container.withEnv( "NEO4J_AUTH", auth )
                 .withEnv( "NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes" )
                 .withEnv( confNames.get( Setting.BACKUP_ENABLED ).envName, "true" )
                 .withEnv( confNames.get( Setting.BACKUP_LISTEN_ADDRESS ).envName, "0.0.0.0:6362" )
                 .withExposedPorts( 7474, 7687, 6362 )
                 .withLogConsumer( new Slf4jLogConsumer( log ) )
                 .waitingFor(WaitStrategies.waitForNeo4jReady( password ));
        if(!asDefaultUser)
        {
            SetContainerUser.nonRootUser( container );
        }
        return container;
    }

    private GenericContainer createAdminContainer( boolean asDefaultUser )
    {
        GenericContainer container = new GenericContainer( TestSettings.ADMIN_IMAGE_ID );
        container.withEnv( "NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes" )
                 .withLogConsumer( new Slf4jLogConsumer( log ) );
        WaitStrategies.waitUntilContainerFinished( container, Duration.ofSeconds( 180) );
        if(!asDefaultUser)
        {
            SetContainerUser.nonRootUser( container );
        }
        return container;
    }

    @Test
    void shouldBackupAndRestore_defaultUser_noAuth() throws Exception
    {
        testCanBackupAndRestore( true, "none" );
    }
    @Test
    void shouldBackupAndRestore_nonDefaultUser_noAuth() throws Exception
    {
        testCanBackupAndRestore( false, "none" );
    }
    @Test
    void shouldBackupAndRestore_defaultUser_withAuth() throws Exception
    {
        testCanBackupAndRestore( true, "secretpassword" );
    }
    @Test
    void shouldBackupAndRestore_nonDefaultUser_withAuth() throws Exception
    {
        testCanBackupAndRestore( false, "secretpassword" );
    }

    private void testCanBackupAndRestore(boolean asDefaultUser, String password) throws Exception
    {
        final String dbUser = "neo4j";
        Path backupDir;

        // BACKUP
        // start a database and populate data
        try(GenericContainer neo4j = createDBContainer( asDefaultUser, password ))
        {
            Path dataDir = temporaryFolderManager.createFolderAndMountAsVolume(neo4j, "/data");
            neo4j.start();
            DatabaseIO dbio = new DatabaseIO( neo4j );
            dbio.putInitialDataIntoContainer( dbUser, password );
            dbio.verifyInitialDataInContainer( dbUser, password );

            // start admin container to initiate backup
            String neoDBAddress = neo4j.getHost() + ":" + neo4j.getMappedPort( 6362 );
            try(GenericContainer adminBackup = createAdminContainer( asDefaultUser ))
            {
                adminBackup.withNetworkMode( "host" )
                           .waitingFor( new LogMessageWaitStrategy().withRegEx( "^Backup command completed.*" ) )
                           .withCommand( "neo4j-admin",
                                         "database",
                                         "backup",
                                         "--to-path=/backups",
                                         "--include-metadata=all",
                                         "--from=" + neoDBAddress,
                                         "neo4j" );

                backupDir = temporaryFolderManager.createFolderAndMountAsVolume(adminBackup, "/backups");
                adminBackup.start();

                Assertions.assertTrue( neo4j.isRunning(), "neo4j container should still be running" );
                dbio.verifyInitialDataInContainer( dbUser, password );
            } //adminBackup goes out of scope here

            // find backup file name and verify its existence.
            List<Path> backupFolder = Files.list( backupDir )
                                           .filter( p -> p.toFile().getName().startsWith( "neo4j" ) )
                                           .toList();
            Assertions.assertEquals( 1, backupFolder.size(), "No backup file was created" );
            File backupFile = backupFolder.get( 0 ).toFile();


            // RESTORE

            // write more stuff
            dbio.putMoreDataIntoContainer( dbUser, password );
            dbio.verifyMoreDataIntoContainer( dbUser, password, true );
            // stop database in preparation for restore
            dbio.runCypherQuery( dbUser, password, "STOP DATABASE neo4j", "system" );

            // do restore
            try(GenericContainer adminRestore = createAdminContainer( asDefaultUser ))
            {
                adminRestore.waitingFor( Wait.forLogMessage( ".*Restore of database .* completed successfully.*", 1 )
                                             .withStartupTimeout( Duration.ofSeconds( 180 ) ) )
                            .withCommand( "neo4j-admin",
                                          "database",
                                          "restore",
                                          "--overwrite-destination=true",
                                          "--from-path=/backups/" + backupFile.getName(),
                                          "neo4j" );
                temporaryFolderManager.mountHostFolderAsVolume( adminRestore, backupDir, "/backups" );
                temporaryFolderManager.mountHostFolderAsVolume( adminRestore, dataDir, "/data" );
                adminRestore.start();
                dbio.runCypherQuery( dbUser, password, "START DATABASE neo4j", "system" );

                // verify new stuff is missing
                dbio.verifyMoreDataIntoContainer( dbUser, password, false );
            } //adminRestore out of scope here
        } // neo4j container goes out of scope here
    }
}

```

--------------------------------------------------------------------------------
/neo4j/docker-neo4j/src/test/java/com/neo4j/docker/utils/Neo4jVersionTest.java:
--------------------------------------------------------------------------------

```java
package com.neo4j.docker.utils;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

public class Neo4jVersionTest {

    @Test
    public void testIsOlderThan_majorDifferent()
    {
        Neo4jVersion newer = new Neo4jVersion( 3, 5, 1 );
        Neo4jVersion older = new Neo4jVersion( 2, 0, 0 );
        testOlderVsNewer( newer, older );
    }

    @Test
    public void testIsOlderThan_minorDifferent()
    {
        Neo4jVersion newer = new Neo4jVersion( 3, 5, 1 );
        Neo4jVersion older = new Neo4jVersion( 3, 0, 0 );
        testOlderVsNewer( newer, older );
    }

    @Test
    public void testIsOlderThan_patchDifferent()
    {
        Neo4jVersion newer = new Neo4jVersion( 3, 5, 1 );
        Neo4jVersion older = new Neo4jVersion( 3, 5, 0 );
        testOlderVsNewer( newer, older );
    }

    @Test
    public void testIsOlderThan_majorLessMinorMore()
    {
        Neo4jVersion newer = new Neo4jVersion( 3, 5, 1 );
        Neo4jVersion older = new Neo4jVersion( 2, 7, 0 );
        testOlderVsNewer( newer, older );
    }

    @Test
    public void testIsOlderThan_minorLessPatchMore()
    {
        Neo4jVersion newer = new Neo4jVersion( 3, 5, 0 );
        Neo4jVersion older = new Neo4jVersion( 3, 2, 12 );
        testOlderVsNewer( newer, older );
    }

    @Test
    public void testSamePatch_isEqualAndAtLeast()
    {
        Neo4jVersion newer = new Neo4jVersion( 3, 5, 0 );
        Neo4jVersion older = new Neo4jVersion( 3, 5, 0 );

        Assertions.assertFalse( newer.isOlderThan( older ) ,
                                String.format( "Didn't detect that %s is newer than %s using isOlderThan", newer, older ) );
        Assertions.assertFalse( older.isNewerThan( newer ) ,
                     String.format( "Didn't detect that %s is older than %s using isNewerThan", older, newer ) );
        Assertions.assertTrue( newer.isAtLeastVersion( older ),
                    String.format( "Didn't detect that %s is at least %s using isAtLeastVersion", newer, older ) );
        Assertions.assertTrue( newer.isEqual(older),
                    String.format( "Didn't detect that %s is equal to %s using isEqual", newer, older ) );
    }

    @Test
    public void testSamePatch_isEqualAndAtLeast_differentBuild()
    {
        Neo4jVersion newer = new Neo4jVersion( 3, 5, 0, "-11" );
        Neo4jVersion older = new Neo4jVersion( 3, 5, 0, "-10" );

        Assertions.assertFalse( newer.isOlderThan( older ) ,
                     String.format( "Didn't detect that %s is newer than %s using isOlderThan", newer, older ) );
        Assertions.assertFalse( older.isNewerThan( newer ) ,
                     String.format( "Didn't detect that %s is older than %s using isNewerThan", older, newer ) );
        Assertions.assertTrue( newer.isAtLeastVersion( older ),
                    String.format( "Didn't detect that %s is at least %s using isAtLeastVersion", newer, older ) );
        Assertions.assertTrue( newer.isEqual(older),
                    String.format( "Didn't detect that %s is equal to %s using isEqual", newer, older ) );
    }

    private void testOlderVsNewer( Neo4jVersion newer, Neo4jVersion older )
    {
        // isOlderThan
        Assertions.assertTrue( older.isOlderThan( newer ) ,
                    String.format( "Didn't detect that %s is older than %s using isOlderThan", older, newer ) );
        Assertions.assertFalse( newer.isOlderThan( older ) ,
                     String.format( "Didn't detect that %s is newer than %s using isOlderThan", newer, older ) );

        // isNewerThan
        Assertions.assertTrue( newer.isNewerThan( older ),
                    String.format( "Didn't detect that %s is newer than %s using isNewerThan", newer, older ) );
        Assertions.assertFalse( older.isNewerThan( newer ) ,
                     String.format( "Didn't detect that %s is older than %s using isNewerThan", older, newer ) );

        // isAtLeastVersion
        Assertions.assertTrue( newer.isAtLeastVersion( older ),
                    String.format( "Didn't detect that %s is newer than %s using isAtLeastVersion", newer, older ) );
        Assertions.assertFalse( older.isAtLeastVersion( newer ) ,
                     String.format( "Didn't detect that %s is older than %s using isAtLeastVersion", older, newer ) );

        Assertions.assertFalse( newer.isEqual(older),
                                String.format( "%s incorrectly is equal to %s", newer, older ) );
        Assertions.assertNotEquals( newer, older, String.format( "%s incorrectly is equal to %s", newer, older ) );
    }

    @Test
    public void testIsOlderThan_sameReleaseReturnsFalse()
    {
        Neo4jVersion version = new Neo4jVersion( 3, 5, 1 );

        Assertions.assertFalse( version.isOlderThan( version ) ,
                     "A release should not be older than itself" );
        Assertions.assertFalse( version.isNewerThan( version ) ,
                     "A release should not be newer than itself" );
    }

    @Test
    public void testFromVersionString_releaseFormat()
    {
        Neo4jVersion version = Neo4jVersion.fromVersionString( "4.4.7" );
        Assertions.assertEquals( 4, version.major, "Did not parse major number from " + version );
        Assertions.assertEquals( 4, version.minor, "Did not parse minor number from " + version );
        Assertions.assertEquals( 7, version.patch, "Did not parse patch number from " + version );
    }

    @Test
    public void testFromVersionString_BSPFormat()
    {
        Neo4jVersion version = Neo4jVersion.fromVersionString( "4.4.7-12345" );
        Assertions.assertEquals( 4, version.major, "Did not parse major number from " + version );
        Assertions.assertEquals( 4, version.minor, "Did not parse minor number from " + version );
        Assertions.assertEquals( 7, version.patch, "Did not parse patch number from " + version );
        Assertions.assertEquals( "-12345", version.label, "Did not build number from " + version );
    }

    @Test
    public void testFromVersionString_calver_releaseFormat()
    {
        Neo4jVersion version = Neo4jVersion.fromVersionString( "2024.10.0" );
        Assertions.assertEquals( 2024, version.major, "Did not parse major number from " + version );
        Assertions.assertEquals( 10, version.minor, "Did not parse minor number from " + version );
        Assertions.assertEquals( 0, version.patch, "Did not parse patch number from " + version );
    }

    @Test
    public void testFromVersionString_calver_BSPFormat()
    {
        Neo4jVersion version = Neo4jVersion.fromVersionString( "2024.10.0-1234" );
        Assertions.assertEquals( 2024, version.major, "Did not parse major number from " + version );
        Assertions.assertEquals( 10, version.minor, "Did not parse minor number from " + version );
        Assertions.assertEquals( 0, version.patch, "Did not parse patch number from " + version );
        Assertions.assertEquals( "-1234", version.label, "Did not build number from " + version );
    }

    @Test
    public void testFromVersionString_calver_OneMonthDigit()
    {
        // an incorrect format, but would be useful if it was handled correctly
        Neo4jVersion version = Neo4jVersion.fromVersionString( "2027.1.5-99" );
        Assertions.assertEquals( 2027, version.major, "Did not parse major number from " + version );
        Assertions.assertEquals( 1, version.minor, "Did not parse minor number from " + version );
        Assertions.assertEquals( 5, version.patch, "Did not parse patch number from " + version );
        Assertions.assertEquals( "-99", version.label, "Did not build number from " + version );
    }

    @Test
    void testToStringCalVer()
    {
        Neo4jVersion version = Neo4jVersion.fromVersionString( "2025.2.0-1234" );
        String outStr = version.toString();
        Assertions.assertEquals( "2025.02.0-1234", outStr );
    }
}

```

--------------------------------------------------------------------------------
/neo4j/docker-neo4j/docker-image-src/3.1/docker-entrypoint.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/bash -eu

cmd="$1"

function running_as_root
{
    test "$(id -u)" = "0"
}

# If we're running as root, then run as the neo4j user. Otherwise
# docker is running with --user and we simply use that user.  Note
# that su-exec, despite its name, does not replicate the functionality
# of exec, so we need to use both
if running_as_root; then
  userid="neo4j"
  groupid="neo4j"
  exec_cmd="exec su-exec neo4j"
else
  userid="$(id -u)"
  groupid="$(id -g)"
  exec_cmd="exec"
fi
readonly userid
readonly groupid
readonly exec_cmd

# Need to chown the home directory - but a user might have mounted a
# volume here (notably a conf volume). So take care not to chown
# volumes (stuff not owned by neo4j)
if running_as_root; then
  # Non-recursive chown for the base directory
  chown "${userid}":"${groupid}" "${NEO4J_HOME}"
  chmod 700 "${NEO4J_HOME}"
fi

while IFS= read -r -d '' dir
do
  if running_as_root && [[ "$(stat -c %U "${dir}")" = "neo4j" ]]; then
    # Using mindepth 1 to avoid the base directory here so recursive is OK
    chown -R "${userid}":"${groupid}" "${dir}"
    chmod -R 700 "${dir}"
  fi
done <   <(find "${NEO4J_HOME}" -type d -mindepth 1 -maxdepth 1 -print0)

# Data dir is chowned later

if [ "${cmd}" == "dump-config" ]; then
  if [ -d /conf ]; then
    ${exec_cmd} cp --recursive "${NEO4J_HOME}"/conf/* /conf
    exit 0
  else
    echo >&2 "You must provide a /conf volume"
    exit 1
  fi
fi

# Env variable naming convention:
# - prefix NEO4J_
# - double underscore char '__' instead of single underscore '_' char in the setting name
# - underscore char '_' instead of dot '.' char in the setting name
# Example:
# NEO4J_dbms_tx__log_rotation_retention__policy env variable to set
#       dbms.tx_log.rotation.retention_policy setting

# Backward compatibility - map old hardcoded env variables into new naming convention (if they aren't set already)
# Set some to default values if unset
: ${NEO4J_dbms_tx__log_rotation_retention__policy:=${NEO4J_dbms_txLog_rotation_retentionPolicy:-"100M size"}}
: ${NEO4J_wrapper_java_additional:=${NEO4J_UDC_SOURCE:-"-Dneo4j.ext.udc.source=docker"}}
: ${NEO4J_dbms_memory_heap_initial__size:=${NEO4J_dbms_memory_heap_maxSize:-"512M"}}
: ${NEO4J_dbms_memory_heap_max__size:=${NEO4J_dbms_memory_heap_maxSize:-"512M"}}
: ${NEO4J_dbms_unmanaged__extension__classes:=${NEO4J_dbms_unmanagedExtensionClasses:-}}
: ${NEO4J_dbms_allow__format__migration:=${NEO4J_dbms_allowFormatMigration:-}}
: ${NEO4J_dbms_connectors_default__advertised__address:=${NEO4J_dbms_connectors_defaultAdvertisedAddress:-}}
: ${NEO4J_ha_server__id:=${NEO4J_ha_serverId:-}}
: ${NEO4J_ha_initial__hosts:=${NEO4J_ha_initialHosts:-}}
: ${NEO4J_causal__clustering_expected__core__cluster__size:=${NEO4J_causalClustering_expectedCoreClusterSize:-}}
: ${NEO4J_causal__clustering_initial__discovery__members:=${NEO4J_causalClustering_initialDiscoveryMembers:-}}
: ${NEO4J_causal__clustering_discovery__listen__address:=${NEO4J_causalClustering_discoveryListenAddress:-"0.0.0.0:5000"}}
: ${NEO4J_causal__clustering_discovery__advertised__address:=${NEO4J_causalClustering_discoveryAdvertisedAddress:-"$(hostname):5000"}}
: ${NEO4J_causal__clustering_transaction__listen__address:=${NEO4J_causalClustering_transactionListenAddress:-"0.0.0.0:6000"}}
: ${NEO4J_causal__clustering_transaction__advertised__address:=${NEO4J_causalClustering_transactionAdvertisedAddress:-"$(hostname):6000"}}
: ${NEO4J_causal__clustering_raft__listen__address:=${NEO4J_causalClustering_raftListenAddress:-"0.0.0.0:7000"}}
: ${NEO4J_causal__clustering_raft__advertised__address:=${NEO4J_causalClustering_raftAdvertisedAddress:-"$(hostname):7000"}}

: ${NEO4J_dbms_connectors_default__listen__address:="0.0.0.0"}
: ${NEO4J_dbms_connector_http_listen__address:="0.0.0.0:7474"}
: ${NEO4J_dbms_connector_https_listen__address:="0.0.0.0:7473"}
: ${NEO4J_dbms_connector_bolt_listen__address:="0.0.0.0:7687"}
: ${NEO4J_ha_host_coordination:="$(hostname):5001"}
: ${NEO4J_ha_host_data:="$(hostname):6001"}

# unset old hardcoded unsupported env variables
unset NEO4J_dbms_txLog_rotation_retentionPolicy NEO4J_UDC_SOURCE \
    NEO4J_dbms_memory_heap_maxSize NEO4J_dbms_memory_heap_maxSize \
    NEO4J_dbms_unmanagedExtensionClasses NEO4J_dbms_allowFormatMigration \
    NEO4J_dbms_connectors_defaultAdvertisedAddress NEO4J_ha_serverId \
    NEO4J_ha_initialHosts NEO4J_causalClustering_expectedCoreClusterSize \
    NEO4J_causalClustering_initialDiscoveryMembers \
    NEO4J_causalClustering_discoveryListenAddress \
    NEO4J_causalClustering_discoveryAdvertisedAddress \
    NEO4J_causalClustering_transactionListenAddress \
    NEO4J_causalClustering_transactionAdvertisedAddress \
    NEO4J_causalClustering_raftListenAddress \
    NEO4J_causalClustering_raftAdvertisedAddress

if [ -d /conf ]; then
    find /conf -type f -exec cp {} "${NEO4J_HOME}"/conf \;
fi

if [ -d /ssl ]; then
    NEO4J_dbms_directories_certificates="/ssl"
fi

if [ -d /plugins ]; then
    NEO4J_dbms_directories_plugins="/plugins"
fi

if [ -d /logs ]; then
    NEO4J_dbms_directories_logs="/logs"
fi

if [ -d /import ]; then
    NEO4J_dbms_directories_import="/import"
fi

if [ -d /metrics ]; then
    NEO4J_dbms_directories_metrics="/metrics"
fi

# set the neo4j initial password only if you run the database server
if [ "${cmd}" == "neo4j" ]; then
    if [ "${NEO4J_AUTH:-}" == "none" ]; then
        NEO4J_dbms_security_auth__enabled=false
    elif [[ "${NEO4J_AUTH:-}" == neo4j/* ]]; then
        password="${NEO4J_AUTH#neo4j/}"
        if [ "${password}" == "neo4j" ]; then
            echo >&2 "Invalid value for password. It cannot be 'neo4j', which is the default."
            exit 1
        fi
        # Will exit with error if users already exist (and print a message explaining that)
        bin/neo4j-admin set-initial-password "${password}" || true
    elif [ -n "${NEO4J_AUTH:-}" ]; then
        echo >&2 "Invalid value for NEO4J_AUTH: '${NEO4J_AUTH}'"
        exit 1
    fi
fi

# list env variables with prefix NEO4J_ and create settings from them
unset NEO4J_AUTH NEO4J_SHA256 NEO4J_TARBALL
for i in $( set | grep ^NEO4J_ | awk -F'=' '{print $1}' | sort -rn ); do
    setting=$(echo ${i} | sed 's|^NEO4J_||' | sed 's|_|.|g' | sed 's|\.\.|_|g')
    value=$(echo ${!i})
    # Don't allow settings with no value or settings that start with a number (neo4j converts settings to env variables and you cannot have an env variable that starts with a number)
    if [[ -n ${value} ]]; then
        if [[ ! "${setting}" =~ ^[0-9]+.*$ ]]; then
            if grep -q -F "${setting}=" "${NEO4J_HOME}"/conf/neo4j.conf; then
                # Remove any lines containing the setting already
                sed --in-place "/^${setting}=.*/d" "${NEO4J_HOME}"/conf/neo4j.conf
            fi
            # Then always append setting to file
            echo "${setting}=${value}" >> "${NEO4J_HOME}"/conf/neo4j.conf
        else
            echo >&2 "WARNING: ${setting} not written to conf file because settings that start with a number are not permitted"
        fi
    fi
done

# Chown the data dir now that (maybe) an initial password has been
# set (this is a file in the data dir)
if running_as_root; then
  chmod -R 777 /data
  chown -R "${userid}":"${groupid}" /data
fi

# if we're running as root and the logs directory is not writable by the neo4j user, then chown it.
# this situation happens if no user is passed to docker run and the /logs directory is mounted.
if running_as_root && [[ "$(stat -c %U /logs)" != "neo4j" ]]; then
#if [[ $(stat -c %u /logs) != $(id -u "${userid}") ]]; then
    echo "/logs directory is not writable. Changing the directory owner to ${userid}:${groupid}"
    # chown the log dir if it's not writable
    chmod -R 777 /logs
    chown -R "${userid}":"${groupid}" /logs
fi

# If we're running as a non-default user and we can't write to the logs directory then user needs to change directory permissions manually.
# This happens if a user is passed to docker run and an unwritable log directory is mounted.
if ! running_as_root && [[ ! -w /logs ]]; then
    echo "User does not have write permissions to mounted log directory."
    echo "Manually grant write permissions for the directory and try again."
    exit 1
fi

[ -f "${EXTENSION_SCRIPT:-}" ] && . ${EXTENSION_SCRIPT}

# Use su-exec to drop privileges to neo4j user
# Note that su-exec, despite its name, does not replicate the
# functionality of exec, so we need to use both
if [ "${cmd}" == "neo4j" ]; then
  ${exec_cmd} neo4j console
else
  ${exec_cmd} "$@"
fi

```

--------------------------------------------------------------------------------
/neo4j/docker-neo4j/docker-image-src/3.2/docker-entrypoint.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/bash -eu

cmd="$1"

function running_as_root
{
    test "$(id -u)" = "0"
}

# If we're running as root, then run as the neo4j user. Otherwise
# docker is running with --user and we simply use that user.  Note
# that su-exec, despite its name, does not replicate the functionality
# of exec, so we need to use both
if running_as_root; then
  userid="neo4j"
  groupid="neo4j"
  exec_cmd="exec su-exec neo4j"
else
  userid="$(id -u)"
  groupid="$(id -g)"
  exec_cmd="exec"
fi
readonly userid
readonly groupid
readonly exec_cmd

# Need to chown the home directory - but a user might have mounted a
# volume here (notably a conf volume). So take care not to chown
# volumes (stuff not owned by neo4j)
if running_as_root; then
  # Non-recursive chown for the base directory
  chown "${userid}":"${groupid}" "${NEO4J_HOME}"
  chmod 700 "${NEO4J_HOME}"
fi

while IFS= read -r -d '' dir
do
  if running_as_root && [[ "$(stat -c %U "${dir}")" = "neo4j" ]]; then
    # Using mindepth 1 to avoid the base directory here so recursive is OK
    chown -R "${userid}":"${groupid}" "${dir}"
    chmod -R 700 "${dir}"
  fi
done <   <(find "${NEO4J_HOME}" -type d -mindepth 1 -maxdepth 1 -print0)

# Data dir is chowned later

if [ "${cmd}" == "dump-config" ]; then
  if [ -d /conf ]; then
    ${exec_cmd} cp --recursive "${NEO4J_HOME}"/conf/* /conf
    exit 0
  else
    echo >&2 "You must provide a /conf volume"
    exit 1
  fi
fi

# Env variable naming convention:
# - prefix NEO4J_
# - double underscore char '__' instead of single underscore '_' char in the setting name
# - underscore char '_' instead of dot '.' char in the setting name
# Example:
# NEO4J_dbms_tx__log_rotation_retention__policy env variable to set
#       dbms.tx_log.rotation.retention_policy setting

# Backward compatibility - map old hardcoded env variables into new naming convention (if they aren't set already)
# Set some to default values if unset
: ${NEO4J_dbms_tx__log_rotation_retention__policy:=${NEO4J_dbms_txLog_rotation_retentionPolicy:-"100M size"}}
: ${NEO4J_wrapper_java_additional:=${NEO4J_UDC_SOURCE:-"-Dneo4j.ext.udc.source=docker"}}
: ${NEO4J_dbms_memory_heap_initial__size:=${NEO4J_dbms_memory_heap_maxSize:-"512M"}}
: ${NEO4J_dbms_memory_heap_max__size:=${NEO4J_dbms_memory_heap_maxSize:-"512M"}}
: ${NEO4J_dbms_unmanaged__extension__classes:=${NEO4J_dbms_unmanagedExtensionClasses:-}}
: ${NEO4J_dbms_allow__format__migration:=${NEO4J_dbms_allowFormatMigration:-}}
: ${NEO4J_dbms_connectors_default__advertised__address:=${NEO4J_dbms_connectors_defaultAdvertisedAddress:-}}
: ${NEO4J_ha_server__id:=${NEO4J_ha_serverId:-}}
: ${NEO4J_ha_initial__hosts:=${NEO4J_ha_initialHosts:-}}
: ${NEO4J_causal__clustering_expected__core__cluster__size:=${NEO4J_causalClustering_expectedCoreClusterSize:-}}
: ${NEO4J_causal__clustering_initial__discovery__members:=${NEO4J_causalClustering_initialDiscoveryMembers:-}}
: ${NEO4J_causal__clustering_discovery__listen__address:=${NEO4J_causalClustering_discoveryListenAddress:-"0.0.0.0:5000"}}
: ${NEO4J_causal__clustering_discovery__advertised__address:=${NEO4J_causalClustering_discoveryAdvertisedAddress:-"$(hostname):5000"}}
: ${NEO4J_causal__clustering_transaction__listen__address:=${NEO4J_causalClustering_transactionListenAddress:-"0.0.0.0:6000"}}
: ${NEO4J_causal__clustering_transaction__advertised__address:=${NEO4J_causalClustering_transactionAdvertisedAddress:-"$(hostname):6000"}}
: ${NEO4J_causal__clustering_raft__listen__address:=${NEO4J_causalClustering_raftListenAddress:-"0.0.0.0:7000"}}
: ${NEO4J_causal__clustering_raft__advertised__address:=${NEO4J_causalClustering_raftAdvertisedAddress:-"$(hostname):7000"}}

: ${NEO4J_dbms_connectors_default__listen__address:="0.0.0.0"}
: ${NEO4J_dbms_connector_http_listen__address:="0.0.0.0:7474"}
: ${NEO4J_dbms_connector_https_listen__address:="0.0.0.0:7473"}
: ${NEO4J_dbms_connector_bolt_listen__address:="0.0.0.0:7687"}
: ${NEO4J_ha_host_coordination:="$(hostname):5001"}
: ${NEO4J_ha_host_data:="$(hostname):6001"}

# unset old hardcoded unsupported env variables
unset NEO4J_dbms_txLog_rotation_retentionPolicy NEO4J_UDC_SOURCE \
    NEO4J_dbms_memory_heap_maxSize NEO4J_dbms_memory_heap_maxSize \
    NEO4J_dbms_unmanagedExtensionClasses NEO4J_dbms_allowFormatMigration \
    NEO4J_dbms_connectors_defaultAdvertisedAddress NEO4J_ha_serverId \
    NEO4J_ha_initialHosts NEO4J_causalClustering_expectedCoreClusterSize \
    NEO4J_causalClustering_initialDiscoveryMembers \
    NEO4J_causalClustering_discoveryListenAddress \
    NEO4J_causalClustering_discoveryAdvertisedAddress \
    NEO4J_causalClustering_transactionListenAddress \
    NEO4J_causalClustering_transactionAdvertisedAddress \
    NEO4J_causalClustering_raftListenAddress \
    NEO4J_causalClustering_raftAdvertisedAddress

if [ -d /conf ]; then
    find /conf -type f -exec cp {} "${NEO4J_HOME}"/conf \;
fi

if [ -d /ssl ]; then
    NEO4J_dbms_directories_certificates="/ssl"
fi

if [ -d /plugins ]; then
    NEO4J_dbms_directories_plugins="/plugins"
fi

if [ -d /logs ]; then
    NEO4J_dbms_directories_logs="/logs"
fi

if [ -d /import ]; then
    NEO4J_dbms_directories_import="/import"
fi

if [ -d /metrics ]; then
    NEO4J_dbms_directories_metrics="/metrics"
fi

# set the neo4j initial password only if you run the database server
if [ "${cmd}" == "neo4j" ]; then
    if [ "${NEO4J_AUTH:-}" == "none" ]; then
        NEO4J_dbms_security_auth__enabled=false
    elif [[ "${NEO4J_AUTH:-}" == neo4j/* ]]; then
        password="${NEO4J_AUTH#neo4j/}"
        if [ "${password}" == "neo4j" ]; then
            echo >&2 "Invalid value for password. It cannot be 'neo4j', which is the default."
            exit 1
        fi
        # Will exit with error if users already exist (and print a message explaining that)
        bin/neo4j-admin set-initial-password "${password}" || true
    elif [ -n "${NEO4J_AUTH:-}" ]; then
        echo >&2 "Invalid value for NEO4J_AUTH: '${NEO4J_AUTH}'"
        exit 1
    fi
fi

# list env variables with prefix NEO4J_ and create settings from them
unset NEO4J_AUTH NEO4J_SHA256 NEO4J_TARBALL
for i in $( set | grep ^NEO4J_ | awk -F'=' '{print $1}' | sort -rn ); do
    setting=$(echo ${i} | sed 's|^NEO4J_||' | sed 's|_|.|g' | sed 's|\.\.|_|g')
    value=$(echo ${!i})
    # Don't allow settings with no value or settings that start with a number (neo4j converts settings to env variables and you cannot have an env variable that starts with a number)
    if [[ -n ${value} ]]; then
        if [[ ! "${setting}" =~ ^[0-9]+.*$ ]]; then
            if grep -q -F "${setting}=" "${NEO4J_HOME}"/conf/neo4j.conf; then
                # Remove any lines containing the setting already
                sed --in-place "/^${setting}=.*/d" "${NEO4J_HOME}"/conf/neo4j.conf
            fi
            # Then always append setting to file
            echo "${setting}=${value}" >> "${NEO4J_HOME}"/conf/neo4j.conf
        else
            echo >&2 "WARNING: ${setting} not written to conf file because settings that start with a number are not permitted"
        fi
    fi
done

# Chown the data dir now that (maybe) an initial password has been
# set (this is a file in the data dir)
if running_as_root; then
  chmod -R 777 /data
  chown -R "${userid}":"${groupid}" /data
fi

# if we're running as root and the logs directory is not writable by the neo4j user, then chown it.
# this situation happens if no user is passed to docker run and the /logs directory is mounted.
if running_as_root && [[ "$(stat -c %U /logs)" != "neo4j" ]]; then
#if [[ $(stat -c %u /logs) != $(id -u "${userid}") ]]; then
    echo "/logs directory is not writable. Changing the directory owner to ${userid}:${groupid}"
    # chown the log dir if it's not writable
    chmod -R 777 /logs
    chown -R "${userid}":"${groupid}" /logs
fi

# If we're running as a non-default user and we can't write to the logs directory then user needs to change directory permissions manually.
# This happens if a user is passed to docker run and an unwritable log directory is mounted.
if ! running_as_root && [[ ! -w /logs ]]; then
    echo "User does not have write permissions to mounted log directory."
    echo "Manually grant write permissions for the directory and try again."
    exit 1
fi

[ -f "${EXTENSION_SCRIPT:-}" ] && . ${EXTENSION_SCRIPT}

# Use su-exec to drop privileges to neo4j user
# Note that su-exec, despite its name, does not replicate the
# functionality of exec, so we need to use both
if [ "${cmd}" == "neo4j" ]; then
  ${exec_cmd} neo4j console
else
  ${exec_cmd} "$@"
fi

```

--------------------------------------------------------------------------------
/neo4j/docker-neo4j/DEVELOPMENT.md:
--------------------------------------------------------------------------------

```markdown
# Supported platforms

Development is tested on Ubuntu and OSX. It will probably work on other Linuxes.

# Prerequisites

## OSX only

1. install GNU Make (>=4.0)
1. install the Docker Toolbox. See: https://docs.docker.com/install/

## Linux

1. install the Docker Toolbox. See https://docs.docker.com/install/

# Building the Image

There are two supported base operating systems that the docker image can be build upon:
 * debian, based off `debian:bullseye-slim`.
 * RedHat ubi9, based off `redhat/ubi9-minimal`. Only available for 4.4 onwards.

On top of that there is also the choice Neo4j version, and whether to build `community` or `enterprise` edition Neo4j.

## Just give me the build TLDR

You probably want to create the neo4j image with whatever base image and community/enterprise variant you need, then tag it like a released neo4j version.

Here are some examples:
```bash
# 5.9.0 community edition and debian 
NEO4JVERSION=5.9.0 make tag-debian-community
# creates tag neo4j:5.9.0-debian

# 4.4.20 community edition and redhat ubi9
NEO4JVERSION=4.4.20 make tag-ubi9-community
# creates tag neo4j:4.4.20-ubi9

# 5.2.0 enterprise edition and debian 
NEO4JVERSION=5.2.0 make tag-debian-enterprise
# creates tag neo4j:5.2.0-enterprise-debian

# 4.4.0 enterprise edition and redhat ubi9
NEO4JVERSION=4.4.0 make tag-ubi9-enterprise
# creates tag neo4j:4.4.0-enterprise-ubi9
```

## The build script

The build script [build-docker-image.sh](./build-docker-image.sh) will take these options and produce a Neo4j image and a neo4j-admin image, for the combination you request.
For example:
```bash
#  debian based 4.4.22 community edition:
./build-docker-image.sh 4.4.22 community debian
#  redhat-ubi9 based 5.9.0 enterprise edition:
./build-docker-image.sh 5.9.0 enterprise ubi9
```
The make script will automatically download the source files needed to build the images.
You just need to specify the **full** Neo4j version including major, minor and patch numbers.

The source code (entrypoint, Dockerfile and so on) is outputted into the `build/<base OS>/coredb/<edition>` and `build/<base OS>/neo4j-admin/<edition>` folders.

The resulting images will have a randomly generated tag, which is written into the files `build/<base OS>/coredb/.image-id-<edition>` and `build/<base OS>/neo4j-admin/.image-id-<edition>`.

## Using the Convenience Makefile

The [Makefile](./Makefile) is a wrapper around the [build-docker-image.sh](./build-docker-image.sh).
It mostly just adds extra functionality to help you build lots of images at once, or does extra steps like tagging, testing and generating release files.

The four actions it can do are:
* `build`
* `test`
* `tag`
* `package`

For each action, it can be broken down by base image and community/enterprise type.
For example `build`, has the following make targets:
* `build`. Builds *every* variant.
* `build-debian`. Builds debian community and enterprise.
* `build-ubi9`. Builds redhat-ubi9 community and enterprise.
* `build-debian-community`
* `build-debian-enterprise`
* `build-ubi9-community`
* `build-ubi9-enterprise`

The other actions have the same targets.

This is an example of calling one of the build targets:
```bash
NEO4JVERSION=4.4.4 make clean build-debian
```
This will build community and enterprise, coredb and neo4j-admin, all based on debian.

To build and then tag all debian neo4j images, use `tag`. For example:
```bash
NEO4JVERSION=4.4.4 make clean tag-debian
```

## Building ARM64 based images

From Neo4j 4.4.0 onwards, the Neo4j image should be buildable on any architecture using the same build commands as [Building the Image](#building-the-image).

### Building ARM versions before 4.4
Earlier versions of Neo4j are no longer under active development and have not been tested on ARM architectures, even when those versions were under development.

It is strongly advised that you use 4.4.0 or later on an ARM system.

If you really must use an unsupported Neo4j version then in your clone of this repository, `git checkout` tag `neo4j-4.3.23` and follow development instructions there.
https://github.com/neo4j/docker-neo4j/blob/neo4j-4.3.23/DEVELOPMENT.md#building-arm64-based-images


## If the Neo4j Version is not Publicly Available

The make script cannot automatically download unreleased source files, so you need to manually download them before building the images.

1. Assuming you cloned this repository to `$NEO4J_DOCKER_ROOT`, 
download the community and enterprise unix tar.gz files from the `packaging` build in our pipeline, and copy them to `$NEO4J_DOCKER_ROOT/in`.
1. Run the make script setting `NEO4JVERSION` to the version number in the files downloaded into the `in/` folder.

For example: 

```bash
$ cd $NEO4J_DOCKER_ROOT
$ ls $NEO4J_DOCKER_ROOT/in
  neo4j-community-4.0.0-alpha05-unix.tar.gz  neo4j-enterprise-4.0.0-alpha05-unix.tar.gz

$ NEO4JVERSION=4.0.0-alpha05 make clean build
``` 

### If building an image from your local Neo4j repository

This isn't recommended since you will need to package your Neo4j tar with the browser so that neo4j will be responsive on 7474 and 7687.

1. Clone the Neo4j github repository and checkout the branch you want.
3. Run `mvn install` plus whatever maven build flags you like. This should install the latest neo4j jars into the maven cache.
4. Copy the community and enterprise tar.gz files to `$NEO4J_DOCKER_ROOT/in`.
5. Use the `NEO4JVERSION` that is in the pom file of your Neo4j repository clone to build the docker image, e.g.:
```shell
$ NEO4JVERSION=5.5.0-SNAPSHOT make clean build
```

# Running the Tests

The tests are written in java, and require Maven plus JDK 17 (any JDK distributions should work, we use OpenJDK).

The tests require some information about the image before they can test it. 
These can be passed as an environment variable or a command line parameter when invoking maven:


| Env Variable    | Maven parameter | Description                                                |
|-----------------|-----------------|------------------------------------------------------------|
| `NEO4JVERSION`  | `-Dversion`     | the Neo4j version of the image                             |
| `NEO4J_IMAGE`   | `-Dimage`       | the tag of the image to test                               |
| `NEO4JADMIN_IMAGE` | `-Dadminimage` | the tag of the neo4j-admin image to test           |
| `NEO4J_EDITION` | `-Dedition`     | Either `community` or `enterprise` depending on the image. |

<!-- prettified with http://www.tablesgenerator.com/markdown_tables -->

## Using Maven
The Makefile can run the entire test suite.
1. Make sure `java --version` is java 17.
2. `NEO4JVERSION=<VERSION> make test-<BASE OS>` This is a make target that will run these commands:
```bash
mvn test -Dimage=$(cat build/<BASE OS>/coredb/.image-id-enterprise) -Dadminimage=$(cat build/<BASE OS>/neo4j-admin/.image-id-enterprise) -Dedition=enterprise -Dversion=${NEO4JVERSION}
mvn test -Dimage=$(cat build/<BASE OS>/coredb/.image-id-community) -Dadminimage=$(cat build/<BASE OS>/neo4j-admin/.image-id-community) -Dedition=community -Dversion=${NEO4JVERSION}
```

## In Intellij

1. Make sure the project SDK is java 17.
3. Install the [EnvFile](https://plugins.jetbrains.com/plugin/7861-envfile) Intellij plugin.
5. Under Run Configurations edit the Template JUnit configuration:
   1. Select the "EnvFile" tab
   2. Make sure "Enable EnvFile" is checked.
   3. Click the `+` then click to add a `.env` file.
   4. In the file selection box select `./build/<BASE OS>/devenv-enterprise.env` or `./build/<BASE OS>/devenv-community.env` depending on which one you want to test. If you do not have the `./build` directory, build the docker image and it will be created.
   5. Rebuilding the Neo4j image will regenerate the `.env` files, so you don't need to worry about keeping the environment up to date.

You should now be able to run unit tests straight from the IDE.


## Running with podman

Tests in this module are using testcontainers. The framework expects you to have docker available on your system.
And there are some issues like described here: https://github.com/testcontainers/testcontainers-java/issues/2088

TLDR on what you need to do to be able to use podman:

1. Make sure you have podman service running. For example: ```podman system service --time=0 unix:///tmp/podman.sock```

2. Add those environment variables:
```
DOCKER_HOST=unix:///tmp/podman.sock;
TESTCONTAINERS_RYUK_DISABLED=true;
TESTCONTAINERS_CHECKS_DISABLE=true 
```

# Troubleshooting
## cannot find symbol `com.sun.security.auth.module.UnixSystem`

This can happen if you switch from java 17 to java 11 (or the other way) and then try to rebuild the tests in Intellij.

Check that the `java.version` property in the [pom.xml file](../master/pom.xml) is set to 17.


```

--------------------------------------------------------------------------------
/neo4j/docker-neo4j/src/test/java/com/neo4j/docker/coredb/plugins/TestSemVerPluginMatching.java:
--------------------------------------------------------------------------------

```java
package com.neo4j.docker.coredb.plugins;

import com.neo4j.docker.utils.DatabaseIO;
import com.neo4j.docker.utils.HttpServerTestExtension;
import com.neo4j.docker.utils.Neo4jVersion;
import com.neo4j.docker.utils.TemporaryFolderManager;
import com.neo4j.docker.utils.TestSettings;
import com.neo4j.docker.utils.WaitStrategies;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.Testcontainers;
import org.testcontainers.containers.Container;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.output.Slf4jLogConsumer;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.neo4j.docker.utils.TestSettings.NEO4J_VERSION;

public class TestSemVerPluginMatching
{
    private static final String DB_USER = "neo4j";
    private static final String DB_PASSWORD = "qualityPassword123";
    private final Logger log = LoggerFactory.getLogger(TestSemVerPluginMatching.class);

    @RegisterExtension
    public static TemporaryFolderManager temporaryFolderManager = new TemporaryFolderManager();
    @RegisterExtension
    public HttpServerTestExtension httpServer = new HttpServerTestExtension();
    StubPluginHelper stubPluginHelper = new StubPluginHelper(httpServer);


    private GenericContainer<?> createContainerWithTestPlugin()
    {
        Testcontainers.exposeHostPorts( httpServer.PORT );
        GenericContainer<?> container = new GenericContainer<>( TestSettings.IMAGE_ID );

        container.withEnv( "NEO4J_AUTH", DB_USER + "/" + DB_PASSWORD )
                .withEnv( "NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes" )
                .withEnv( "NEO4J_DEBUG", "yes" )
                .withEnv( Neo4jPluginEnv.get(), "[\"" + StubPluginHelper.PLUGIN_ENV_NAME + "\"]" )
                .withExposedPorts( 7474, 7687 )
                .withLogConsumer( new Slf4jLogConsumer( log ) )
                .waitingFor( WaitStrategies.waitForNeo4jReady(DB_PASSWORD));
        return container;
    }

    @Test
    void testSemanticVersioningLogic() throws Exception
    {
        // testing common neo4j name variants
        List<String> neo4jVersions = new ArrayList<String>()
        {{
            add( NEO4J_VERSION.toReleaseString() );
            add( NEO4J_VERSION.toReleaseString() + "-12345" );
        }};

        List<String> matchingCases = new ArrayList<String>()
        {{
            add( NEO4J_VERSION.toReleaseString() );
            add( Neo4jVersion.makeVersionString( NEO4J_VERSION.major, NEO4J_VERSION.minor)+".x" );
            add( Neo4jVersion.makeVersionString( NEO4J_VERSION.major, NEO4J_VERSION.minor)+".*" );
            add( NEO4J_VERSION.major + ".x.x" );
            add( NEO4J_VERSION.major + ".*.*" );
            add( "x.x.x" );
            add( "*.*.*" );
        }};

        List<String> nonMatchingCases = new ArrayList<String>()
        {{
            add( Neo4jVersion.makeVersionString( NEO4J_VERSION.major+1, NEO4J_VERSION.minor)+".x" );
            add( Neo4jVersion.makeVersionString( NEO4J_VERSION.major-1, NEO4J_VERSION.minor)+".x" );
            add( Neo4jVersion.makeVersionString( NEO4J_VERSION.major, NEO4J_VERSION.minor+1)+".x" );
            add( Neo4jVersion.makeVersionString( NEO4J_VERSION.major, NEO4J_VERSION.minor-1)+".x" );
            add( Neo4jVersion.makeVersionString( NEO4J_VERSION.major+1, NEO4J_VERSION.minor)+".*" );
            add( Neo4jVersion.makeVersionString( NEO4J_VERSION.major-1, NEO4J_VERSION.minor)+".*" );
            add( Neo4jVersion.makeVersionString( NEO4J_VERSION.major, NEO4J_VERSION.minor+1)+".*" );
            add( Neo4jVersion.makeVersionString( NEO4J_VERSION.major, NEO4J_VERSION.minor-1)+".*" );
        }};

        // Asserting every test case means that if there's a failure, all further tests won't run.
        // Instead we're running all tests and saving any failed cases for reporting at the end of the test.
        List<String> failedTests = new ArrayList<String>();

        try ( GenericContainer container = createContainerWithTestPlugin() )
        {
            container.withEnv( Neo4jPluginEnv.get(), "" ); // don't need the _testing plugin for this
            container.start();

            String semverQuery = "echo \"{\\\"neo4j\\\":\\\"%s\\\"}\" | " +
                    "jq -L/startup --raw-output \"import \\\"semver\\\" as lib; " +
                    ".neo4j | lib::semver(\\\"%s\\\")\"";
            for ( String verToBeMatched : neo4jVersions )
            {
                for ( String verRegex : matchingCases )
                {
                    Container.ExecResult out = container.execInContainer( "sh", "-c", String.format( semverQuery, verRegex, verToBeMatched ) );
                    if ( !out.getStdout().trim().equals( "true" ) )
                    {
                        failedTests.add( String.format( "%s should match %s but did not", verRegex, verToBeMatched ) );
                    }
                }
                for ( String verRegex : nonMatchingCases )
                {
                    Container.ExecResult out = container.execInContainer( "sh", "-c", String.format( semverQuery, verRegex, verToBeMatched ) );
                    if ( !out.getStdout().trim().equals( "false" ) )
                    {
                        failedTests.add( String.format( "%s should NOT match %s but did", verRegex, verToBeMatched ) );
                    }
                }
            }
            if ( !failedTests.isEmpty() )
            {
                Assertions.fail( failedTests.stream().collect( Collectors.joining( "\n" ) ) );
            }
        }
    }

    @Test
    void testSemanticVersioningPlugin_catchesMatchWithX() throws Exception
    {
        Path pluginsDir = temporaryFolderManager.createFolder("plugins");
        stubPluginHelper.createStubPluginForVersion(pluginsDir,
            Neo4jVersion.makeVersionString( NEO4J_VERSION.major, NEO4J_VERSION.minor)+".x");
        try ( GenericContainer container = createContainerWithTestPlugin() )
        {
            container.start();
            DatabaseIO db = new DatabaseIO( container );
            stubPluginHelper.verifyStubPluginLoaded( db, DB_USER, DB_PASSWORD );
        }
    }

    @Test
    void testSemanticVersioningPlugin_catchesMatchWithStar() throws Exception
    {
        Path pluginsDir = temporaryFolderManager.createFolder("plugins");
        stubPluginHelper.createStubPluginForVersion(pluginsDir,
            Neo4jVersion.makeVersionString( NEO4J_VERSION.major, NEO4J_VERSION.minor)+".*");
        try ( GenericContainer container = createContainerWithTestPlugin() )
        {
            container.start();
            DatabaseIO db = new DatabaseIO( container );
            stubPluginHelper.verifyStubPluginLoaded( db, DB_USER, DB_PASSWORD );
        }
    }

    @Test
    void testSemanticVersioningPlugin_prefersExactMatch() throws Exception
    {
        verifySemanticVersioningPrefersBetterMatches(new HashMap<String,String>()
            {{
                put( "x.x.x", "notareal.jar" );
                put( NEO4J_VERSION.major + ".x.x", "notareal.jar" );
                put( Neo4jVersion.makeVersionString( NEO4J_VERSION.major, NEO4J_VERSION.minor) + ".x", "notareal.jar" );
                put( NEO4J_VERSION.toString(), StubPluginHelper.PLUGIN_FILENAME);
            }} );
    }

    @Test
    void testSemanticVersioningPlugin_prefersMajorMinorMatch() throws Exception
    {
        verifySemanticVersioningPrefersBetterMatches(new HashMap<String,String>()
            {{
                put( "x.x.x", "notareal.jar" );
                put( NEO4J_VERSION.major + ".x.x", "notareal.jar" );
                put( Neo4jVersion.makeVersionString( NEO4J_VERSION.major, NEO4J_VERSION.minor) + ".x",
                     StubPluginHelper.PLUGIN_FILENAME);
            }} );
    }

    @Test
    void testSemanticVersioningPlugin_prefersMajorMatch() throws Exception
    {
        verifySemanticVersioningPrefersBetterMatches(new HashMap<String,String>()
            {{
                put( "x.x.x", "notareal.jar" );
                put( NEO4J_VERSION.major + ".x.x", StubPluginHelper.PLUGIN_FILENAME);
            }} );
    }

    void verifySemanticVersioningPrefersBetterMatches(Map<String, String> versionsInJson) throws Exception {
        Path pluginsDir = temporaryFolderManager.createFolder("plugins");
        stubPluginHelper.createStubPluginsForVersionMapping(pluginsDir, versionsInJson);
        try (GenericContainer container = createContainerWithTestPlugin()) {
            container.start();
            DatabaseIO db = new DatabaseIO(container);
            // if semver did not pick exact version match then it will load a non-existent plugin instead and fail.
            stubPluginHelper.verifyStubPluginLoaded(db, DB_USER, DB_PASSWORD);
        }
    }
}

```

--------------------------------------------------------------------------------
/neo4j/docker-neo4j/src/test/java/com/neo4j/docker/coredb/configurations/TestExtendedConf.java:
--------------------------------------------------------------------------------

```java
package com.neo4j.docker.coredb.configurations;

import com.neo4j.docker.utils.Neo4jVersion;
import com.neo4j.docker.utils.SetContainerUser;
import com.neo4j.docker.utils.WaitStrategies;
import com.neo4j.docker.utils.TemporaryFolderManager;
import com.neo4j.docker.utils.TestSettings;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.BindMode;
import org.testcontainers.containers.ContainerLaunchException;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.output.OutputFrame;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;
import org.testcontainers.containers.wait.strategy.Wait;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission;
import java.time.Duration;
import java.util.HashSet;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

public class TestExtendedConf
{
	private final Logger log = LoggerFactory.getLogger( TestExtendedConf.class );
    private static Path testConfsFolder;
    private static Configuration logRotationConfig;
    @RegisterExtension
    public static TemporaryFolderManager temporaryFolderManager = new TemporaryFolderManager();

	@BeforeAll
	static void ensureFeaturePresent()
	{
		Assumptions.assumeTrue( TestSettings.NEO4J_VERSION.isNewerThan( new Neo4jVersion( 4,2,0 ) ),
								"Extended configuration feature not available before 4.2" );
	}

    @BeforeAll
    static void createVersionSpecificConfigurationSettings() {
        testConfsFolder = Configuration.getConfigurationResourcesFolder();
        logRotationConfig = Configuration.getConfigurationNameMap()
                                         .get( Setting.LOGS_GC_ROTATION_KEEPNUMBER );
    }

	protected GenericContainer createContainer(String password)
	{
        GenericContainer container = new GenericContainer( TestSettings.IMAGE_ID )
                .withEnv( "NEO4J_AUTH", password == null || password.isEmpty() ? "none" : "neo4j/" + password )
                .withEnv( "NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes" )
                .withEnv( "EXTENDED_CONF", "yeppers" )
                .withExposedPorts( 7474, 7687 )
                .withLogConsumer( new Slf4jLogConsumer( log ) )
                .waitingFor( WaitStrategies.waitForBoltReady());
       return container;
    }


	@ParameterizedTest
	@ValueSource(strings = {"", "supersecretpassword"})
	public void shouldStartWithExtendedConf(String password)
	{
        try(GenericContainer container = createContainer(password))
        {
            container.start();

            Assertions.assertTrue( container.isRunning() );
			assertPasswordChangedLogIsCorrect( password, container );
		}
	}

	private void assertPasswordChangedLogIsCorrect( String password, GenericContainer container )
	{
		if ( password.isEmpty()) {
			Assertions.assertFalse( container.getLogs( OutputFrame.OutputType.STDOUT)
                                             .contains( "Changed password for user 'neo4j'." ) );
		} else {
			Assertions.assertTrue( container.getLogs( OutputFrame.OutputType.STDOUT)
                                            .contains( "Changed password for user 'neo4j'." ) );
		}
	}

	@ParameterizedTest
	@ValueSource(strings = {"", "supersecretpassword"})
	void testReadsTheExtendedConfFile_defaultUser(String password) throws Exception
	{
		// set up test folders
		Path confFolder = temporaryFolderManager.createFolder("conf");
		Path logsFolder = temporaryFolderManager.createFolder("logs");

		// copy configuration file and set permissions
		Path confFile = testConfsFolder.resolve( "ExtendedConf.conf" );
		Files.copy( confFile, confFolder.resolve( "neo4j.conf" ) );
        chmodConfFilePermissions( confFolder.resolve( "neo4j.conf" ) );
		temporaryFolderManager.setFolderOwnerToNeo4j( confFolder.resolve( "neo4j.conf" ) );

		// start  container
		try(GenericContainer container = createContainer(password))
		{
			runContainerAndVerify( container, confFolder, logsFolder, password );
		}
	}

    @ParameterizedTest
    @ValueSource( strings = {"", "supersecretpassword"} )
    void testInvalidExtendedConfFile_nonRootUser( String password ) throws Exception
    {
        // set up test folder
        Path confFolder = temporaryFolderManager.createFolder("conf");

        // copy configuration file and set permissions
        Files.copy( testConfsFolder.resolve( "InvalidExtendedConf.conf" ), confFolder.resolve( "neo4j.conf" ) );
        chmodConfFilePermissions( confFolder.resolve( "neo4j.conf" ) );

        try(GenericContainer container = createContainer( password ))
        {
            SetContainerUser.nonRootUser( container );
            container.withFileSystemBind( "/etc/passwd", "/etc/passwd", BindMode.READ_ONLY );
            container.withFileSystemBind( "/etc/group", "/etc/group", BindMode.READ_ONLY );
            temporaryFolderManager.mountHostFolderAsVolume( container, confFolder, "/conf" );
            container.setStartupCheckStrategy( new OneShotStartupCheckStrategy().withTimeout( Duration.ofSeconds( 30 ) ) );
            container.setWaitStrategy(
                    Wait.forLogMessage( ".*this is an error message from inside neo4j config command expansion.*", 1 )
                        .withStartupTimeout( Duration.ofSeconds( 30 ) ) );

            Assertions.assertThrows( ContainerLaunchException.class,
                                     () -> container.start(),
                                     "Container should have errored on start");

            String logs = container.getLogs();
            // check that error messages from neo4j are visible in docker logs
            Assertions.assertTrue( logs.contains( "Error evaluating value for setting '" + logRotationConfig.name + "'" ) );
            // check that error messages from the command that failed are visible in docker logs
            Assertions.assertTrue( logs.contains( "this is an error message from inside neo4j config command expansion" ) );
            // check that the error is only encountered once (i.e. we quit the docker entrypoint the first time it was encountered)
            Assertions.assertEquals( 1, countOccurrences( Pattern.compile( "Error evaluating value for setting" ), logs ) );
        }
    }

	private int countOccurrences( Pattern pattern, String inString )
	{
		Matcher matcher = pattern.matcher( inString );
		int count = 0;
		while ( matcher.find() )
		{
			count = count + 1;
		}
		return count;
	}

	@ParameterizedTest
	@ValueSource(strings = {"", "supersecretpassword"})
	void testReadsTheExtendedConfFile_nonRootUser(String password) throws Exception
	{
		// set up test folders
		Path confFolder = temporaryFolderManager.createFolder("conf");
		Path logsFolder = temporaryFolderManager.createFolder("logs");

		// copy configuration file and set permissions
		Path confFile = testConfsFolder.resolve( "ExtendedConf.conf" );
		Files.copy( confFile, confFolder.resolve( "neo4j.conf" ) );
		chmodConfFilePermissions( confFolder.resolve( "neo4j.conf" ) );

		try(GenericContainer container = createContainer(password))
		{
			SetContainerUser.nonRootUser( container );
			container.withFileSystemBind( "/etc/passwd", "/etc/passwd", BindMode.READ_ONLY );
			container.withFileSystemBind( "/etc/group", "/etc/group", BindMode.READ_ONLY );
			runContainerAndVerify( container, confFolder, logsFolder, password );
		}
	}

	private void runContainerAndVerify(GenericContainer container, Path confFolder, Path logsFolder, String password) throws Exception
	{
		temporaryFolderManager.mountHostFolderAsVolume( container, confFolder, "/conf" );
		temporaryFolderManager.mountHostFolderAsVolume( container, logsFolder, "/logs" );

		container.start();

		Path debugLog = logsFolder.resolve("debug.log");
		Assertions.assertTrue(debugLog.toFile().exists(), "Did not write debug log");

		//Check if the container reads the conf file
		Stream<String> lines = Files.lines( debugLog);
		Optional<String> isMatch = lines.filter( s -> s.contains(logRotationConfig.name + "=20")).findFirst();
		lines.close();
		Assertions.assertTrue(  isMatch.isPresent(), logRotationConfig.name+" was not set correctly");

		//Check the password was changed if set
		assertPasswordChangedLogIsCorrect( password, container );
	}

	private void chmodConfFilePermissions( Path file ) throws IOException
	{

		HashSet<PosixFilePermission> permissions = new HashSet<PosixFilePermission>()
		{{
			add( PosixFilePermission.OWNER_READ );
			add( PosixFilePermission.OWNER_WRITE );
		}};

		if ( TestSettings.NEO4J_VERSION.isAtLeastVersion( new Neo4jVersion( 4, 3, 0 ) ) )
		{
			permissions.add( PosixFilePermission.GROUP_READ );
		}
		Files.setPosixFilePermissions( file, permissions );
	}
}

```

--------------------------------------------------------------------------------
/neo4j/docker-neo4j/src/test/java/com/neo4j/docker/coredb/TestAdminReport.java:
--------------------------------------------------------------------------------

```java
package com.neo4j.docker.coredb;

import com.neo4j.docker.utils.DatabaseIO;
import com.neo4j.docker.utils.Neo4jVersion;
import com.neo4j.docker.utils.SetContainerUser;
import com.neo4j.docker.utils.WaitStrategies;
import com.neo4j.docker.utils.TemporaryFolderManager;
import com.neo4j.docker.utils.TestSettings;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.Container;
import org.testcontainers.containers.ContainerLaunchException;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.output.OutputFrame;
import org.testcontainers.containers.output.Slf4jLogConsumer;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.List;


public class TestAdminReport
{
    private final Logger log = LoggerFactory.getLogger( TestAdminReport.class );
    private final String PASSWORD = "supersecretpassword";
    @RegisterExtension
    public static TemporaryFolderManager temporaryFolderManager = new TemporaryFolderManager();
    private static String reportDestinationFlag;

    @BeforeAll
    static void setCorrectPathFlagForVersion()
    {
        if( TestSettings.NEO4J_VERSION.isOlderThan( Neo4jVersion.NEO4J_VERSION_500 ) )
        {
            reportDestinationFlag = "--to";
        }
        else
        {
            reportDestinationFlag = "--to-path";
        }
    }

    private GenericContainer createNeo4jContainer( boolean asCurrentUser)
    {
        GenericContainer container = new GenericContainer( TestSettings.IMAGE_ID )
                .withEnv( "NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes" )
                .withEnv( "NEO4J_AUTH", "neo4j/"+PASSWORD )
                .withExposedPorts( 7474, 7687 )
                .withLogConsumer( new Slf4jLogConsumer( log ) )
                .waitingFor(WaitStrategies.waitForNeo4jReady( PASSWORD ));
        if(asCurrentUser)
        {
            SetContainerUser.nonRootUser( container );
        }
        return container;
    }

    @ParameterizedTest(name = "ascurrentuser_{0}")
    @ValueSource(booleans = {true, false})
    void testMountToTmpReports(boolean asCurrentUser) throws Exception
    {
        try(GenericContainer container = createNeo4jContainer(asCurrentUser))
        {
            temporaryFolderManager.createFolderAndMountAsVolume(container, "/logs");
            Path reportFolder = temporaryFolderManager.createFolderAndMountAsVolume(container, "/tmp/reports");
            container.start();
            DatabaseIO dbio = new DatabaseIO( container );
            dbio.putInitialDataIntoContainer( "neo4j", PASSWORD );

            Container.ExecResult execResult = container.execInContainer( "neo4j-admin-report" );
            verifyCreatesReport( reportFolder, execResult );
        }
    }

    @ParameterizedTest(name = "ascurrentuser_{0}")
    @ValueSource(booleans = {true, false})
    void testCanWriteReportToAnyMountedLocation_toPathWithEquals(boolean asCurrentUser) throws Exception
    {
        String reportFolderName = "reportAnywhere-"+ (asCurrentUser? "currentuser-":"defaultuser-") + "withEqualsArg-";
        verifyCanWriteToMountedLocation( asCurrentUser,
                                         reportFolderName,
                                         new String[]{"neo4j-admin-report", "--verbose", reportDestinationFlag+"=/reports"} );
    }

    @ParameterizedTest(name = "ascurrentuser_{0}")
    @ValueSource(booleans = {true, false})
    void testCanWriteReportToAnyMountedLocation_toPathWithSpace(boolean asCurrentUser) throws Exception
    {
        String reportFolderName = "reportAnywhere-"+ (asCurrentUser? "currentuser-":"defaultuser-") + "withSpaceArg-";
        verifyCanWriteToMountedLocation( asCurrentUser,
                                         reportFolderName,
                                         new String[]{"neo4j-admin-report", "--verbose", reportDestinationFlag, "/reports"} );
    }

    private void verifyCanWriteToMountedLocation(boolean asCurrentUser, String testFolderPrefix, String[] execArgs) throws Exception
    {
        try(GenericContainer container = createNeo4jContainer(asCurrentUser))
        {
            temporaryFolderManager.createFolderAndMountAsVolume(container, "/logs");
            Path reportFolder = temporaryFolderManager.createFolderAndMountAsVolume(container, "/reports");
            container.start();
            DatabaseIO dbio = new DatabaseIO( container );
            dbio.putInitialDataIntoContainer( "neo4j", PASSWORD );
            Container.ExecResult execResult = container.execInContainer(execArgs);
            // log exec results, because the results of an exec don't get logged automatically.
            log.info( execResult.getStdout() );
            log.warn( execResult.getStderr() );
            verifyCreatesReport( reportFolder, execResult );
        }
    }

    @Test
    void shouldShowNeo4jAdminHelpText_whenCMD() throws Exception
    {
        try(GenericContainer container = createNeo4jContainer(false))
        {
            container.withCommand( "neo4j-admin-report", "--help" );
            WaitStrategies.waitUntilContainerFinished( container, Duration.ofSeconds( 20 ) );
            try
            {
                container.start();
            }
            catch ( ContainerLaunchException e )
            {
                // consume any failed to start exceptions
                log.warn( "Running 'neo4j-admin-report --help' caused the container to fail rather than " +
                          "successfully complete. This is allowable, so the test is not going to fail." );
            }
            verifyHelpText( container.getLogs(OutputFrame.OutputType.STDOUT),
                            container.getLogs(OutputFrame.OutputType.STDERR) );
        }
    }

    @Test
    void shouldShowNeo4jAdminHelpText_whenEXEC() throws Exception
    {
        try(GenericContainer container = createNeo4jContainer(false))
        {
            temporaryFolderManager.createFolderAndMountAsVolume(container, "/logs");
            container.start();
            Container.ExecResult execResult = container.execInContainer( "neo4j-admin-report", "--help" );
            // log exec results, because the results of an exec don't get logged automatically.
            log.info( "STDOUT:\n" + execResult.getStdout() );
            log.warn( "STDERR:\n" + execResult.getStderr() );
            verifyHelpText( execResult.getStdout(), execResult.getStderr() );
        }
    }

    private void verifyCreatesReport( Path reportFolder,Container.ExecResult reportExecOut ) throws Exception
    {
        List<File> reports = Files.list( reportFolder )
                                  .map( Path::toFile )
                                  .filter( file -> ! file.isDirectory() )
                                  .toList();
        if( TestSettings.NEO4J_VERSION.isOlderThan( Neo4jVersion.NEO4J_VERSION_500 ) )
        {
            // for some reason neo4j-admin report prints jvm details to stderr
            String[] lines = reportExecOut.getStderr().split( "\n" );
            Assertions.assertEquals( 1, lines.length,
                                     "There were errors during report generation" );
            Assertions.assertTrue( lines[0].startsWith( "Selecting JVM" ),
                                   "There were unexpected error messages in the neo4j-admin report:\n"+reportExecOut.getStderr() );
        }
        else
        {
            Assertions.assertEquals( "", reportExecOut.getStderr(),
                                     "There were errors during report generation" );
        }
        Assertions.assertEquals( 1, reports.size(), "Expected exactly 1 report to be produced" );
        Assertions.assertFalse( reportExecOut.toString().contains( "No running instance of neo4j was found" ),
                                "neo4j-admin could not locate running neo4j database" );
    }

    private void verifyHelpText(String stdout, String stderr)
    {
        // in 4.4 the help text goes in stderr
        if( TestSettings.NEO4J_VERSION.isOlderThan( Neo4jVersion.NEO4J_VERSION_500 ) )
        {
            Assertions.assertTrue( stderr.contains(
                    "Produces a zip/tar of the most common information needed for remote assessments." ) );
            Assertions.assertTrue( stderr.contains( "USAGE" ) );
            Assertions.assertTrue( stderr.contains( "OPTIONS" ) );
        }
        else
        {
            Assertions.assertTrue( stdout.contains(
                    "Produces a zip/tar of the most common information needed for remote assessments." ) );
            Assertions.assertTrue( stdout.contains( "USAGE" ) );
            Assertions.assertTrue( stdout.contains( "OPTIONS" ) );
            Assertions.assertEquals( "", stderr, "There were errors when trying to get neo4j-admin-report help text" );
        }
    }
}

```

--------------------------------------------------------------------------------
/neo4j/docker-neo4j/src/test/java/com/neo4j/docker/coredb/plugins/TestBundledPluginInstallation.java:
--------------------------------------------------------------------------------

```java
package com.neo4j.docker.coredb.plugins;

import com.neo4j.docker.utils.DatabaseIO;
import com.neo4j.docker.utils.Neo4jVersion;
import com.neo4j.docker.utils.TemporaryFolderManager;
import com.neo4j.docker.utils.TestSettings;
import com.neo4j.docker.utils.WaitStrategies;
import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.Container;
import org.testcontainers.containers.ContainerLaunchException;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.output.OutputFrame;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.containers.wait.strategy.HttpWaitStrategy;

import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Tag("BundleTest")
public class TestBundledPluginInstallation
{
    private final Logger log = LoggerFactory.getLogger( TestBundledPluginInstallation.class );
    @RegisterExtension
    public static TemporaryFolderManager temporaryFolderManager = new TemporaryFolderManager();

    static class BundledPlugin
    {
        private final String name;
        private final Neo4jVersion bundledSince;
        private final Neo4jVersion bundledUntil;
        private final boolean isEnterpriseOnly;

        public BundledPlugin(String name, Neo4jVersion bundledSince, @Nullable Neo4jVersion bundledUntil, boolean isEnterpriseOnly)
        {
            this.name = name;
            this.bundledSince = bundledSince;
            this.bundledUntil = bundledUntil;
            this.isEnterpriseOnly = isEnterpriseOnly;
        }

        public boolean shouldBePresentInImage()
        {
            boolean shouldBeBundled = TestSettings.NEO4J_VERSION.isAtLeastVersion( bundledSince );
            if(isEnterpriseOnly)
            {
                shouldBeBundled = shouldBeBundled && (TestSettings.EDITION == TestSettings.Edition.ENTERPRISE);
            }
            if(bundledUntil != null)
            {
                shouldBeBundled = shouldBeBundled && TestSettings.NEO4J_VERSION.isOlderThan( bundledUntil );
            }
            return shouldBeBundled;
        }

        @Override
        public String toString()
        {
            return "BundledPlugin " + name;
        }
    }

    private static final BundledPlugin APOC = new BundledPlugin("apoc",
            new Neo4jVersion(5, 0, 0), null,false);
    private static final BundledPlugin APOC_CORE = new BundledPlugin("apoc-core",
            new Neo4jVersion(4, 3, 15),
            new Neo4jVersion(5, 0, 0), false);
    private static final BundledPlugin BLOOM = new BundledPlugin("bloom",
            Neo4jVersion.NEO4J_VERSION_440, null, true);
    private static final BundledPlugin GDS = new BundledPlugin("graph-data-science",
            Neo4jVersion.NEO4J_VERSION_440, null, true );
    private static final BundledPlugin GENAI = new BundledPlugin("genai",
            new Neo4jVersion(5, 18, 0), null, false);

    static Stream<Arguments> bundledPluginsArgs() {
        return Stream.of(
                Arguments.arguments(APOC_CORE),
                Arguments.arguments(APOC),
                 Arguments.arguments(GDS),
                Arguments.arguments(BLOOM),
                Arguments.arguments(GENAI)
        );
    }

    private GenericContainer createContainer()
    {
        GenericContainer container = new GenericContainer( TestSettings.IMAGE_ID );
        container.withEnv( "NEO4J_AUTH", "none" )
                 .withEnv( "NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes" )
                 .withEnv("NEO4J_DEBUG", "yes")
                 .withExposedPorts( 7474, 7687 )
                 .withLogConsumer( new Slf4jLogConsumer( log ) )
                 .waitingFor( WaitStrategies.waitForBoltReady() );
        return container;
    }

    private GenericContainer createContainerWithBundledPlugin(BundledPlugin plugin)
    {
        return createContainer().withEnv( Neo4jPluginEnv.get(), "[\"" +plugin.name+ "\"]" );
    }

    @ParameterizedTest(name = "testBundledPlugin_{0}")
    @MethodSource("bundledPluginsArgs")
    public void testBundledPlugin(BundledPlugin plugin) throws Exception
    {
        Assumptions.assumeTrue(plugin.shouldBePresentInImage(),
                "test only applies when the plugin "+plugin.name+" is present");

        GenericContainer container = null;
        Path pluginsMount = null;
        try
        {
            container = createContainerWithBundledPlugin(plugin);
            pluginsMount = temporaryFolderManager.createFolderAndMountAsVolume(container, "/plugins");
            container.start();
            DatabaseIO dbio = new DatabaseIO( container );
            dbio.putInitialDataIntoContainer( "neo4j", "none" );
        }
        catch(ContainerLaunchException e)
        {
            // we don't want this test to depend on the plugins actually working (that's outside the scope of
            // the docker tests), so we have to be robust to the container failing to start.
            log.error( String.format("The bundled %s plugin caused Neo4j to fail to start.", plugin.name) );
        }
        finally
        {
            // verify the plugins were loaded.
            // This is done in the finally block because after stopping the container, the stdout cannot be retrieved.
            if (pluginsMount != null)
            {
                List<String> plugins = Files.list(pluginsMount).map( fname -> fname.getFileName().toString() )
                                            .filter( fname -> fname.endsWith( ".jar" ) )
                                            .collect(Collectors.toList());
                Assertions.assertTrue(plugins.size() == 1, "more than one plugin was loaded" );
                Assertions.assertTrue( plugins.get( 0 ).contains( plugin.name ) );
                // Verify from container logs, that the plugins were loaded locally rather than downloaded.
                String logs = container.getLogs( OutputFrame.OutputType.STDOUT);
                String errlogs = container.getLogs( OutputFrame.OutputType.STDERR);
                Assertions.assertTrue(
                        Stream.of(logs.split( "\n" ))
                              .anyMatch( line -> line.matches( "Installing Plugin '" + plugin.name + "' from /var/lib/neo4j/.*" ) ),
                        "Plugin was not installed from neo4j home");
            }
            if(container !=null)
            {
                container.stop();
            }
            else
            {
                Assertions.fail("Test failed before container could even be initialised");
            }
        }
    }

    @Test
    void testPluginLoadsWithAuthentication() throws Exception
    {
        Assumptions.assumeTrue( TestSettings.NEO4J_VERSION.isAtLeastVersion( Neo4jVersion.NEO4J_VERSION_500 ) );

        final String PASSWORD = "12345678";

        try( GenericContainer container = createContainerWithBundledPlugin(BLOOM))
        {
            container.withEnv( "NEO4J_AUTH", "neo4j/"+PASSWORD )
                     .withEnv( "NEO4J_dbms_bloom_license__file", "/licenses/bloom.license" );
            // mounting logs because it's useful for debugging
            temporaryFolderManager.createFolderAndMountAsVolume(container, "/logs");
            Path licenseFolder = temporaryFolderManager.createFolderAndMountAsVolume(container, "/licenses");
            Files.writeString( licenseFolder.resolve("bloom.license"), "notareallicense" );
            // make sure the container successfully starts and we can write to it without getting authentication errors
            container.start();
            DatabaseIO dbio = new DatabaseIO( container );
            dbio.putInitialDataIntoContainer( "neo4j", PASSWORD );
        }
    }

    @Test
    void testBrowserListensOn7474() throws Exception
    {
        try(GenericContainer container = createContainer())
        {
            container.waitingFor( new HttpWaitStrategy()
                                          .forPort(7474)
                                          .forStatusCode(200)
                                          .withStartupTimeout(Duration.ofSeconds(60)) );
            container.start();
            Assertions.assertTrue( container.isRunning() );
            Container.ExecResult r = container.execInContainer( "wget", "-q", "-O", "-", "http://localhost:7474/browser/" );
            Assertions.assertEquals( 0, r.getExitCode(), "Did not get http response from browser");
            Assertions.assertFalse( r.getStdout().isEmpty(), "HTTP response from browser was empty." );
            Assertions.assertTrue( r.getStdout().contains( "Neo4j Browser" ),
                                   "HTTP response from browser did not contain expected information.\n"+r.getStdout());
        }
    }
}

```

--------------------------------------------------------------------------------
/neo4j/docker-neo4j/src/test/java/com/neo4j/docker/TestDockerComposeSecrets.java:
--------------------------------------------------------------------------------

```java
package com.neo4j.docker;

import com.github.dockerjava.api.DockerClient;
import com.neo4j.docker.coredb.configurations.Configuration;
import com.neo4j.docker.coredb.configurations.Setting;
import com.neo4j.docker.utils.DatabaseIO;
import com.neo4j.docker.utils.TemporaryFolderManager;
import com.neo4j.docker.utils.TestSettings;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.DockerClientFactory;
import org.testcontainers.containers.ComposeContainer;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.containers.output.ToStringConsumer;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFilePermissions;

import static com.neo4j.docker.utils.WaitStrategies.waitForBoltReady;

public class TestDockerComposeSecrets
{
    private final Logger log = LoggerFactory.getLogger( TestDockerComposeSecrets.class );

    private static final int DEFAULT_BOLT_PORT = 7687;
    private static final int DEFAULT_HTTP_PORT = 7474;
    private static final Path TEST_RESOURCES_PATH = Paths.get( "src", "test", "resources", "dockersecrets" );

    @RegisterExtension
    public static TemporaryFolderManager temporaryFolderManager = new TemporaryFolderManager();

    @BeforeAll
    public static void skipTestsForARM()
    {
        Assumptions.assumeFalse( System.getProperty( "os.arch" ).equals( "aarch64" ),
                                 "This test is ignored on ARM architecture, because Docker Compose Container doesn't support it." );
    }

    private ComposeContainer createContainer( File composeFile, Path containerRootDir, String serviceName )
    {
        var container = new ComposeContainer( composeFile );

        container.withExposedService( serviceName, DEFAULT_BOLT_PORT )
                 .withExposedService( serviceName, DEFAULT_HTTP_PORT )
                 .withEnv( "NEO4J_IMAGE", TestSettings.IMAGE_ID.asCanonicalNameString() )
                 .withEnv( "HOST_ROOT", containerRootDir.toAbsolutePath().toString() )
                 .waitingFor( serviceName, waitForBoltReady() )
                 .withLogConsumer( serviceName, new Slf4jLogConsumer( log ) );

        return container;
    }

    /* We need to stop the neo4j service before we stop the docker compose container otherwise there is a race condition for
       files that are written in mounted folders. This should not be needed when https://github.com/testcontainers/testcontainers-java/issues/9870 is fixed
    */
    private void stopContainerSafely( ComposeContainer container, String serviceName ) throws IOException
    {
        var containerId = container.getContainerByServiceName( serviceName ).get().getContainerId();

        DockerClient dockerClient = DockerClientFactory.lazyClient();
        dockerClient.stopContainerCmd( containerId ).exec();

        container.stop();
    }

    @Test
    void shouldCreateContainerAndConnect() throws Exception
    {
        var tmpDir = temporaryFolderManager.createFolder( "Simple_Container_Compose" );
        var composeFile = copyDockerComposeResourceFile( tmpDir, TEST_RESOURCES_PATH.resolve( "simple-container-compose.yml" ).toFile() );
        var serviceName = "simplecontainer";

        try ( var dockerComposeContainer = createContainer( composeFile, tmpDir, serviceName ) )
        {
            dockerComposeContainer.start();

            var dbio = new DatabaseIO( dockerComposeContainer.getServiceHost( serviceName, DEFAULT_BOLT_PORT ),
                                       dockerComposeContainer.getServicePort( serviceName, DEFAULT_BOLT_PORT ) );
            dbio.verifyConnectivity( "neo4j", "simplecontainerpassword" );
            stopContainerSafely( dockerComposeContainer, serviceName );
        }
    }

    @Test
    void shouldCreateContainerWithSecretPasswordAndConnect() throws Exception
    {
        var tmpDir = temporaryFolderManager.createFolder( "Container_Compose_With_Secrets" );
        var composeFile = copyDockerComposeResourceFile( tmpDir, TEST_RESOURCES_PATH.resolve( "container-compose-with-secrets.yml" ).toFile() );
        var serviceName = "secretscontainer";

        var newSecretPassword = "neo4j/newSecretPassword";
        Files.createFile( tmpDir.resolve( "neo4j_auth.txt" ) );
        Files.writeString( tmpDir.resolve( "neo4j_auth.txt" ), newSecretPassword );

        try ( var dockerComposeContainer = createContainer( composeFile, tmpDir, serviceName ) )
        {
            dockerComposeContainer.start();
            var dbio = new DatabaseIO( dockerComposeContainer.getServiceHost( serviceName, DEFAULT_BOLT_PORT ),
                                       dockerComposeContainer.getServicePort( serviceName, DEFAULT_BOLT_PORT ) );
            dbio.verifyConnectivity( "neo4j", "newSecretPassword" );
            stopContainerSafely( dockerComposeContainer, serviceName );
        }
    }

    @Test
    void shouldOverrideVariableWithSecretValue() throws Exception
    {
        var tmpDir = temporaryFolderManager.createFolder( "Container_Compose_With_Secrets_Override" );
        Files.createDirectories( tmpDir.resolve( "neo4j" ).resolve( "config" ) );

        var composeFile = copyDockerComposeResourceFile( tmpDir, TEST_RESOURCES_PATH.resolve( "container-compose-with-secrets-override.yml" ).toFile() );
        var serviceName = "secretsoverridecontainer";

        var newSecretPageCache = "50M";
        Files.createFile( tmpDir.resolve( "neo4j_pagecache.txt" ) );
        Files.writeString( tmpDir.resolve( "neo4j_pagecache.txt" ), newSecretPageCache );

        try ( var dockerComposeContainer = createContainer( composeFile, tmpDir, serviceName ) )
        {
            dockerComposeContainer.start();

            var dbio = new DatabaseIO( dockerComposeContainer.getServiceHost( serviceName, DEFAULT_BOLT_PORT ),
                                       dockerComposeContainer.getServicePort( serviceName, DEFAULT_BOLT_PORT ) );

            var secretSetting = dbio.getConfigurationSettingAsString( "neo4j",
                                                                      "secretsoverridecontainerpassword",
                                                                      Configuration.getConfigurationNameMap().get( Setting.MEMORY_PAGECACHE_SIZE ) );

            Assertions.assertTrue( secretSetting.contains( "50" ) );

            stopContainerSafely( dockerComposeContainer, serviceName );
        }
    }

    @Test
    void shouldFailIfSecretFileDoesNotExist() throws Exception
    {
        var tmpDir = temporaryFolderManager.createFolder( "Container_Compose_With_Secrets_Override" );
        var composeFile = copyDockerComposeResourceFile( tmpDir, TEST_RESOURCES_PATH.resolve( "container-compose-with-secrets-override.yml" ).toFile() );
        var serviceName = "secretsoverridecontainer";

        try ( var dockerComposeContainer = createContainer( composeFile, tmpDir, serviceName ) )
        {
            Assertions.assertThrows( Exception.class, dockerComposeContainer::start );
        }
    }

    @Test
    void shouldFailAndPrintMessageIfFileIsNotReadable() throws Exception
    {
        var tmpDir = temporaryFolderManager.createFolder( "Container_Compose_With_Secrets_Override" );
        var composeFile = copyDockerComposeResourceFile( tmpDir, TEST_RESOURCES_PATH.resolve( "container-compose-with-secrets-override.yml" ).toFile() );
        var serviceName = "secretsoverridecontainer";

        Files.createFile( tmpDir.resolve( "neo4j_pagecache.txt" ) );
        Files.writeString( tmpDir.resolve( "neo4j_pagecache.txt" ), "50M" );

        var newPermissions = PosixFilePermissions.fromString( "rw-------" );
        Files.setPosixFilePermissions( tmpDir.resolve( "neo4j_pagecache.txt" ), newPermissions );

        try ( var dockerComposeContainer = createContainer( composeFile, tmpDir, serviceName ) )
        {
            var containerLogConsumer = new ToStringConsumer();
            dockerComposeContainer.withLogConsumer( serviceName, containerLogConsumer );
            var expectedLogLine = "The secret file '/run/secrets/neo4j_dbms_memory_pagecache_size_file' does not exist or is not readable. " +
                                  "Make sure you have correctly configured docker secrets.";
            Assertions.assertThrows( Exception.class, dockerComposeContainer::start );
            Assertions.assertTrue( containerLogConsumer.toUtf8String().contains( expectedLogLine ) );
        }
    }

    @Test
    void shouldIgnoreNonNeo4jFileEnvVars() throws Exception
    {
        var tmpDir = temporaryFolderManager.createFolder( "Simple_Container_Compose_With_File_Var" );
        var composeFile =
                copyDockerComposeResourceFile( tmpDir, TEST_RESOURCES_PATH.resolve( "simple-container-compose-with-external-file-var.yml" ).toFile() );
        var serviceName = "simplecontainer";

        try ( var dockerComposeContainer = createContainer( composeFile, tmpDir, serviceName ) )
        {
            dockerComposeContainer.start();

            var dbio = new DatabaseIO( dockerComposeContainer.getServiceHost( serviceName, DEFAULT_BOLT_PORT ),
                                       dockerComposeContainer.getServicePort( serviceName, DEFAULT_BOLT_PORT ) );
            dbio.verifyConnectivity( "neo4j", "simplecontainerpassword" );

            stopContainerSafely( dockerComposeContainer, serviceName );
        }
    }

    private File copyDockerComposeResourceFile( Path targetDirectory, File resourceFile ) throws IOException
    {
        File compose_file = new File( targetDirectory.toString(), resourceFile.getName() );
        if ( compose_file.exists() )
        {
            Files.delete( compose_file.toPath() );
        }
        Files.copy( resourceFile.toPath(), Paths.get( compose_file.getPath() ) );
        return compose_file;
    }
}

```

--------------------------------------------------------------------------------
/neo4j/docker-neo4j/src/test/java/com/neo4j/docker/coredb/TestUpgrade.java:
--------------------------------------------------------------------------------

```java
package com.neo4j.docker.coredb;

import com.github.dockerjava.api.command.CreateContainerCmd;
import com.github.dockerjava.api.exception.NotFoundException;
import com.github.dockerjava.api.model.Bind;
import com.neo4j.docker.utils.DatabaseIO;
import com.neo4j.docker.utils.Neo4jVersion;
import com.neo4j.docker.utils.TemporaryFolderManager;
import com.neo4j.docker.utils.TestSettings;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.images.RemoteDockerImage;
import org.testcontainers.utility.DockerImageName;

import java.io.IOException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.function.Consumer;

import static org.junit.jupiter.api.Assumptions.assumeTrue;

public class TestUpgrade
{
	private final Logger log = LoggerFactory.getLogger( TestUpgrade.class );
	private final String user = "neo4j";
	private final String password = "verylongpassword";
    @RegisterExtension
    public static TemporaryFolderManager temporaryFolderManager = new TemporaryFolderManager();

	private GenericContainer makeContainer(DockerImageName image)
	{
        GenericContainer container = new GenericContainer<>( image );
        container.withEnv( "NEO4J_AUTH", user + "/" + password )
                 .withEnv( "NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes" )
                 .withExposedPorts( 7474 )
                 .withExposedPorts( 7687 )
                 .withLogConsumer( new Slf4jLogConsumer( log ) );
        return container;
	}

	private static List<Neo4jVersion> upgradableNeo4jVersionsPre5()
	{
		return Arrays.asList( new Neo4jVersion( 3, 5, 35 ),
							  new Neo4jVersion( 4, 4, 0 ),
							  new Neo4jVersion( 4, 4, 25 ));
    }

    private static List<Neo4jVersion> upgradableNeo4jVersions5x()
    {
        // instead of returning ALL 5.x versions just run a few to check that upgrading works and the volume/bind mount
        // settings do not break upgrade.
        // Running every upgrade path used up all the test agent memorry and caused unneccessary failures.
        // We must assume that Neo4j upgrades are fully tested elsewhere, and just make sure that the
        // docker infrastructure doesn't break upgrading.
		return Arrays.asList( new Neo4jVersion( 5, 1, 0 ),
                              new Neo4jVersion( 5, 5, 0 ),
							  new Neo4jVersion( 5, 10, 0 ));
    }

    private static List<Neo4jVersion> upgradableNeo4jVersionsCalVer()
    {
		return Arrays.asList( new Neo4jVersion( 5, 26, 0 ));
    }

    private static void assumeUpgradeSupported( Neo4jVersion upgradeFrom )
    {
        Assumptions.assumeTrue( TestSettings.NEO4J_VERSION.isNewerThan( upgradeFrom ),
                    "cannot upgrade from " + upgradeFrom + " to " + TestSettings.NEO4J_VERSION);
        if(isArm()) Assumptions.assumeTrue( upgradeFrom.isAtLeastVersion( new Neo4jVersion( 4, 4, 0 ) ), "ARM only supported since 4.4" );

        // if we're preparing a new release, then it's possible the version we're upgrading from hasn't been released to
        // dockerhub, so the test will fail when pulling the upgrade-from image.
        // If this happens we should ignore rather than fail the test.
        try
        {
            RemoteDockerImage img = new RemoteDockerImage( getUpgradeFromImage( upgradeFrom ) );
            img.get(); // docker pull
        }
        catch ( NotFoundException nfex )
        {
            // purposely fail an assumption if the image was not found
            Assumptions.assumeTrue( false, "neo4j:"+upgradeFrom+" is not available on dockerhub yet. Ignoring test.");
        }
    }

    private static boolean isArm()
    {
        return System.getProperty( "os.arch" ).equals( "aarch64" );
    }

	@ParameterizedTest(name = "from_{0}")
    @MethodSource( "upgradableNeo4jVersionsPre5" )
	void canUpgradeNeo4j_fileMounts_Pre5( Neo4jVersion upgradeFrom) throws Exception
	{
		assumeTrue( TestSettings.NEO4J_VERSION.isOlderThan( Neo4jVersion.NEO4J_VERSION_500 ),
                    "this test only for upgrades before 5.0: " + TestSettings.NEO4J_VERSION );
		testUpgradeFileMounts( upgradeFrom );
	}

	@ParameterizedTest(name = "from_{0}")
	@MethodSource( "upgradableNeo4jVersionsPre5" )
	void canUpgradeNeo4j_namedVolumes_Pre5(Neo4jVersion upgradeFrom) throws Exception
	{
		assumeTrue( TestSettings.NEO4J_VERSION.isOlderThan( Neo4jVersion.NEO4J_VERSION_500 ),
                    "this test only for upgrades before 5.0: " + TestSettings.NEO4J_VERSION );
		testUpgradeNamedVolumes( upgradeFrom );
	}

	@ParameterizedTest(name = "from_{0}")
    @MethodSource( "upgradableNeo4jVersions5x" )
	void canUpgradeNeo4j_fileMounts_5x( Neo4jVersion upgradeFrom) throws Exception
	{
		assumeTrue( TestSettings.NEO4J_VERSION.isAtLeastVersion( Neo4jVersion.NEO4J_VERSION_500 ),
                    "this test only for upgrades after 5.0: " + TestSettings.NEO4J_VERSION );
		assumeTrue( TestSettings.NEO4J_VERSION.isOlderThan( Neo4jVersion.NEO4J_VERSION_527 ),
                    "this test only for upgrades on the 5x branch" + TestSettings.NEO4J_VERSION );
		testUpgradeFileMounts( upgradeFrom );
	}

	@ParameterizedTest(name = "from_{0}")
	@MethodSource( "upgradableNeo4jVersions5x" )
	void canUpgradeNeo4j_namedVolumes_5x(Neo4jVersion upgradeFrom) throws Exception
	{
		assumeTrue( TestSettings.NEO4J_VERSION.isAtLeastVersion( Neo4jVersion.NEO4J_VERSION_500 ),
                    "this test only for upgrades after 5.0: " + TestSettings.NEO4J_VERSION );
		assumeTrue( TestSettings.NEO4J_VERSION.isOlderThan( Neo4jVersion.NEO4J_VERSION_527 ),
                    "this test only for upgrades on the 5x branch" + TestSettings.NEO4J_VERSION );
		testUpgradeNamedVolumes( upgradeFrom );
	}

	@ParameterizedTest(name = "from_{0}")
    @MethodSource( "upgradableNeo4jVersionsCalVer" )
	void canUpgradeNeo4j_fileMounts_calver( Neo4jVersion upgradeFrom) throws Exception
	{
		assumeTrue( TestSettings.NEO4J_VERSION.isAtLeastVersion( Neo4jVersion.NEO4J_VERSION_527 ),
                    "this test only for upgrades after 5.0: " + TestSettings.NEO4J_VERSION );
		testUpgradeFileMounts( upgradeFrom );
	}

	@ParameterizedTest(name = "from_{0}")
	@MethodSource( "upgradableNeo4jVersionsCalVer" )
	void canUpgradeNeo4j_namedVolumes_calver(Neo4jVersion upgradeFrom) throws Exception
	{
		assumeTrue( TestSettings.NEO4J_VERSION.isAtLeastVersion( Neo4jVersion.NEO4J_VERSION_527 ),
                    "this test only for upgrades after 5.0: " + TestSettings.NEO4J_VERSION );
		testUpgradeNamedVolumes( upgradeFrom );
	}

	private void testUpgradeFileMounts( Neo4jVersion upgradeFrom ) throws IOException
	{
		assumeUpgradeSupported( upgradeFrom );

		Path data, logs, imports, metrics;

		try(GenericContainer container = makeContainer( getUpgradeFromImage( upgradeFrom ) ))
		{
			data = temporaryFolderManager.createFolderAndMountAsVolume(container, "/data");
			logs = temporaryFolderManager.createFolderAndMountAsVolume(container, "/logs");
			imports = temporaryFolderManager.createFolderAndMountAsVolume(container, "/import");
			metrics = temporaryFolderManager.createFolderAndMountAsVolume(container, "/metrics");
			container.start();
			DatabaseIO db = new DatabaseIO( container );
			db.putInitialDataIntoContainer( user, password );
			// stops container cleanly so that neo4j process has enough time to end. The autoclose doesn't seem to block.
			container.getDockerClient().stopContainerCmd( container.getContainerId() ).exec();
		}

		try(GenericContainer container = makeContainer( TestSettings.IMAGE_ID ))
		{
			temporaryFolderManager.mountHostFolderAsVolume( container, data, "/data" );
			temporaryFolderManager.mountHostFolderAsVolume( container, logs, "/logs" );
			temporaryFolderManager.mountHostFolderAsVolume( container, imports, "/import" );
			temporaryFolderManager.mountHostFolderAsVolume( container, metrics, "/metrics" );
			container.withEnv( "NEO4J_dbms_allow__upgrade", "true" );
			container.start();
			DatabaseIO db = new DatabaseIO( container );
			db.verifyInitialDataInContainer( user, password );
		}
	}

	private void testUpgradeNamedVolumes( Neo4jVersion upgradeFrom )
	{
		assumeUpgradeSupported(upgradeFrom);

		String id = String.format( "%04d", new Random().nextInt( 10000 ));
		log.info( "creating volumes with id: "+id );

		try(GenericContainer container = makeContainer(getUpgradeFromImage( upgradeFrom )))
		{
			container.withCreateContainerCmdModifier(
					(Consumer<CreateContainerCmd>) cmd -> cmd.getHostConfig().withBinds(
							Bind.parse("upgrade-conf-"+id+":/conf"),
							Bind.parse("upgrade-data-"+id+":/data"),
							Bind.parse("upgrade-import-"+id+":/import"),
							Bind.parse("upgrade-logs-"+id+":/logs"),
							Bind.parse("upgrade-metrics-"+id+":/metrics"),
							Bind.parse("upgrade-plugins-"+id+":/plugins")
					));
			container.start();
			DatabaseIO db = new DatabaseIO( container );
			db.putInitialDataIntoContainer( user, password );
			container.getDockerClient().stopContainerCmd( container.getContainerId() ).exec();
		}

		try(GenericContainer container = makeContainer( TestSettings.IMAGE_ID ))
		{
			container.withCreateContainerCmdModifier(
					(Consumer<CreateContainerCmd>) cmd -> cmd.getHostConfig().withBinds(
							Bind.parse("upgrade-conf-"+id+":/conf"),
							Bind.parse("upgrade-data-"+id+":/data"),
							Bind.parse("upgrade-import-"+id+":/import"),
							Bind.parse("upgrade-logs-"+id+":/logs"),
							Bind.parse("upgrade-metrics-"+id+":/metrics"),
							Bind.parse("upgrade-plugins-"+id+":/plugins")
					));
			container.withEnv( "NEO4J_dbms_allow__upgrade", "true" );
			container.start();
			DatabaseIO db = new DatabaseIO( container );
			db.verifyInitialDataInContainer( user, password );
		}
	}

	private static DockerImageName getUpgradeFromImage( Neo4jVersion ver)
	{
		if(TestSettings.EDITION == TestSettings.Edition.ENTERPRISE)
		{
			return DockerImageName.parse("neo4j:" + ver.toString() + "-enterprise");
		}
		else
		{
			return DockerImageName.parse("neo4j:" + ver.toString());
		}
	}
}

```

--------------------------------------------------------------------------------
/knowledge_graphs/ai_hallucination_detector.py:
--------------------------------------------------------------------------------

```python
"""
AI Hallucination Detector

Main orchestrator for detecting AI coding assistant hallucinations in Python scripts.
Combines AST analysis, knowledge graph validation, and comprehensive reporting.
"""

import asyncio
import argparse
import logging
import os
import sys
from pathlib import Path
from typing import Optional, List

from dotenv import load_dotenv

from ai_script_analyzer import AIScriptAnalyzer, analyze_ai_script
from knowledge_graph_validator import KnowledgeGraphValidator
from hallucination_reporter import HallucinationReporter

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger(__name__)


class AIHallucinationDetector:
    """Main detector class that orchestrates the entire process"""
    
    def __init__(self, neo4j_uri: str, neo4j_user: str, neo4j_password: str):
        self.validator = KnowledgeGraphValidator(neo4j_uri, neo4j_user, neo4j_password)
        self.reporter = HallucinationReporter()
        self.analyzer = AIScriptAnalyzer()
    
    async def initialize(self):
        """Initialize connections and components"""
        await self.validator.initialize()
        logger.info("AI Hallucination Detector initialized successfully")
    
    async def close(self):
        """Close connections"""
        await self.validator.close()
    
    async def detect_hallucinations(self, script_path: str, 
                                  output_dir: Optional[str] = None,
                                  save_json: bool = True,
                                  save_markdown: bool = True,
                                  print_summary: bool = True) -> dict:
        """
        Main detection function that analyzes a script and generates reports
        
        Args:
            script_path: Path to the AI-generated Python script
            output_dir: Directory to save reports (defaults to script directory)
            save_json: Whether to save JSON report
            save_markdown: Whether to save Markdown report
            print_summary: Whether to print summary to console
        
        Returns:
            Complete validation report as dictionary
        """
        logger.info(f"Starting hallucination detection for: {script_path}")
        
        # Validate input
        if not os.path.exists(script_path):
            raise FileNotFoundError(f"Script not found: {script_path}")
        
        if not script_path.endswith('.py'):
            raise ValueError("Only Python (.py) files are supported")
        
        # Set output directory
        if output_dir is None:
            output_dir = str(Path(script_path).parent)
        
        os.makedirs(output_dir, exist_ok=True)
        
        try:
            # Step 1: Analyze the script using AST
            logger.info("Step 1: Analyzing script structure...")
            analysis_result = self.analyzer.analyze_script(script_path)
            
            if analysis_result.errors:
                logger.warning(f"Analysis warnings: {analysis_result.errors}")
            
            logger.info(f"Found: {len(analysis_result.imports)} imports, "
                       f"{len(analysis_result.class_instantiations)} class instantiations, "
                       f"{len(analysis_result.method_calls)} method calls, "
                       f"{len(analysis_result.function_calls)} function calls, "
                       f"{len(analysis_result.attribute_accesses)} attribute accesses")
            
            # Step 2: Validate against knowledge graph
            logger.info("Step 2: Validating against knowledge graph...")
            validation_result = await self.validator.validate_script(analysis_result)
            
            logger.info(f"Validation complete. Overall confidence: {validation_result.overall_confidence:.1%}")
            
            # Step 3: Generate comprehensive report
            logger.info("Step 3: Generating reports...")
            report = self.reporter.generate_comprehensive_report(validation_result)
            
            # Step 4: Save reports
            script_name = Path(script_path).stem
            
            if save_json:
                json_path = os.path.join(output_dir, f"{script_name}_hallucination_report.json")
                self.reporter.save_json_report(report, json_path)
            
            if save_markdown:
                md_path = os.path.join(output_dir, f"{script_name}_hallucination_report.md")
                self.reporter.save_markdown_report(report, md_path)
            
            # Step 5: Print summary
            if print_summary:
                self.reporter.print_summary(report)
            
            logger.info("Hallucination detection completed successfully")
            return report
            
        except Exception as e:
            logger.error(f"Error during hallucination detection: {str(e)}")
            raise
    
    async def batch_detect(self, script_paths: List[str], 
                          output_dir: Optional[str] = None) -> List[dict]:
        """
        Detect hallucinations in multiple scripts
        
        Args:
            script_paths: List of paths to Python scripts
            output_dir: Directory to save all reports
        
        Returns:
            List of validation reports
        """
        logger.info(f"Starting batch detection for {len(script_paths)} scripts")
        
        results = []
        for i, script_path in enumerate(script_paths, 1):
            logger.info(f"Processing script {i}/{len(script_paths)}: {script_path}")
            
            try:
                result = await self.detect_hallucinations(
                    script_path=script_path,
                    output_dir=output_dir,
                    print_summary=False  # Don't print individual summaries in batch mode
                )
                results.append(result)
                
            except Exception as e:
                logger.error(f"Failed to process {script_path}: {str(e)}")
                # Continue with other scripts
                continue
        
        # Print batch summary
        self._print_batch_summary(results)
        
        return results
    
    def _print_batch_summary(self, results: List[dict]):
        """Print summary of batch processing results"""
        if not results:
            print("No scripts were successfully processed.")
            return
        
        print("\n" + "="*80)
        print("🚀 BATCH HALLUCINATION DETECTION SUMMARY")
        print("="*80)
        
        total_scripts = len(results)
        total_validations = sum(r['validation_summary']['total_validations'] for r in results)
        total_valid = sum(r['validation_summary']['valid_count'] for r in results)
        total_invalid = sum(r['validation_summary']['invalid_count'] for r in results)
        total_not_found = sum(r['validation_summary']['not_found_count'] for r in results)
        total_hallucinations = sum(len(r['hallucinations_detected']) for r in results)
        
        avg_confidence = sum(r['validation_summary']['overall_confidence'] for r in results) / total_scripts
        
        print(f"Scripts Processed: {total_scripts}")
        print(f"Total Validations: {total_validations}")
        print(f"Average Confidence: {avg_confidence:.1%}")
        print(f"Total Hallucinations: {total_hallucinations}")
        
        print(f"\nAggregated Results:")
        print(f"  ✅ Valid: {total_valid} ({total_valid/total_validations:.1%})")
        print(f"  ❌ Invalid: {total_invalid} ({total_invalid/total_validations:.1%})")
        print(f"  🔍 Not Found: {total_not_found} ({total_not_found/total_validations:.1%})")
        
        # Show worst performing scripts
        print(f"\n🚨 Scripts with Most Hallucinations:")
        sorted_results = sorted(results, key=lambda x: len(x['hallucinations_detected']), reverse=True)
        for result in sorted_results[:5]:
            script_name = Path(result['analysis_metadata']['script_path']).name
            hall_count = len(result['hallucinations_detected'])
            confidence = result['validation_summary']['overall_confidence']
            print(f"  - {script_name}: {hall_count} hallucinations ({confidence:.1%} confidence)")
        
        print("="*80)


async def main():
    """Command-line interface for the AI Hallucination Detector"""
    parser = argparse.ArgumentParser(
        description="Detect AI coding assistant hallucinations in Python scripts",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  # Analyze single script
  python ai_hallucination_detector.py script.py
  
  # Analyze multiple scripts
  python ai_hallucination_detector.py script1.py script2.py script3.py
  
  # Specify output directory
  python ai_hallucination_detector.py script.py --output-dir reports/
  
  # Skip markdown report
  python ai_hallucination_detector.py script.py --no-markdown
        """
    )
    
    parser.add_argument(
        'scripts',
        nargs='+',
        help='Python script(s) to analyze for hallucinations'
    )
    
    parser.add_argument(
        '--output-dir',
        help='Directory to save reports (defaults to script directory)'
    )
    
    parser.add_argument(
        '--no-json',
        action='store_true',
        help='Skip JSON report generation'
    )
    
    parser.add_argument(
        '--no-markdown',
        action='store_true',
        help='Skip Markdown report generation'
    )
    
    parser.add_argument(
        '--no-summary',
        action='store_true',
        help='Skip printing summary to console'
    )
    
    parser.add_argument(
        '--neo4j-uri',
        default=None,
        help='Neo4j URI (default: from environment NEO4J_URI)'
    )
    
    parser.add_argument(
        '--neo4j-user',
        default=None,
        help='Neo4j username (default: from environment NEO4J_USER)'
    )
    
    parser.add_argument(
        '--neo4j-password',
        default=None,
        help='Neo4j password (default: from environment NEO4J_PASSWORD)'
    )
    
    parser.add_argument(
        '--verbose',
        action='store_true',
        help='Enable verbose logging'
    )
    
    args = parser.parse_args()
    
    if args.verbose:
        logging.getLogger().setLevel(logging.INFO)
        # Only enable debug for our modules, not neo4j
        logging.getLogger('neo4j').setLevel(logging.WARNING)
        logging.getLogger('neo4j.pool').setLevel(logging.WARNING)
        logging.getLogger('neo4j.io').setLevel(logging.WARNING)
    
    # Load environment variables
    load_dotenv()
    
    # Get Neo4j credentials
    neo4j_uri = args.neo4j_uri or os.environ.get('NEO4J_URI', 'bolt://localhost:7687')
    neo4j_user = args.neo4j_user or os.environ.get('NEO4J_USER', 'neo4j')
    neo4j_password = args.neo4j_password or os.environ.get('NEO4J_PASSWORD', 'password')
    
    if not neo4j_password or neo4j_password == 'password':
        logger.error("Please set NEO4J_PASSWORD environment variable or use --neo4j-password")
        sys.exit(1)
    
    # Initialize detector
    detector = AIHallucinationDetector(neo4j_uri, neo4j_user, neo4j_password)
    
    try:
        await detector.initialize()
        
        # Process scripts
        if len(args.scripts) == 1:
            # Single script mode
            await detector.detect_hallucinations(
                script_path=args.scripts[0],
                output_dir=args.output_dir,
                save_json=not args.no_json,
                save_markdown=not args.no_markdown,
                print_summary=not args.no_summary
            )
        else:
            # Batch mode
            await detector.batch_detect(
                script_paths=args.scripts,
                output_dir=args.output_dir
            )
    
    except KeyboardInterrupt:
        logger.info("Detection interrupted by user")
        sys.exit(1)
    
    except Exception as e:
        logger.error(f"Detection failed: {str(e)}")
        sys.exit(1)
    
    finally:
        await detector.close()


if __name__ == "__main__":
    asyncio.run(main())
```
Page 2/6FirstPrevNextLast