I’m trying to migrate a existing Julia environment to a machine without access to internet. I’ve been searching in discourse if there is already a mature solution for doing this, however, I have only found some hints of how this could be done, e.g., Offline Installation of Julia Packages and Package Install on Offline Cluster (*perhaps I don’t completely understand the solutions).
I was wondering that if forward to 2025 there is a clear way to do this. And if there is, if someone can help me to figure out what I should be doing to make this process consistent.
At this point, I have read things as of copying entire folders and doing pre-builds. In my case, installation of Julia on the machine is not a problem. My interest is on having all the necessary packages in the machine.
I had in mind something simpler, under the assumption that very few people need all 10,000+ packages but rather have a picklist of, say, 100 packages. Try this out in a test environment.
#!/usr/bin/env julia
using Pkg, TOML
import LocalRegistry
import Dates
const PROJECT_NAME = "AirgapProject"
const LOCAL_REGISTRY_NAME = "MyLocalRegistry"
const LOCAL_REGISTRY_PATH = "local_registry"
const PKG_EXPORT_DIR = "compiled_pkgs"
const JULIA_BINARY_SOURCE = "/opt/julia-1.10.2" # <-- CHANGE THIS TO YOUR INSTALL PATH
const JULIA_BINARY_DEST = "julia"
const BUNDLE_NAME = "julia_airgap_bundle.tar.gz"
# Create and activate the project
println("🔧 Creating and activating project [$PROJECT_NAME]")
mkpath(PROJECT_NAME)
Pkg.activate(PROJECT_NAME)
Pkg.instantiate()
# Add your packages
packages = [
"DataFrames", "CSV", "Plots", "HTTP", "GeoMakie"
]
println("📦 Adding packages: ", join(packages, ", "))
Pkg.add(packages)
Pkg.precompile()
# Export packages
println("📦 Exporting packages to [$PKG_EXPORT_DIR]")
Pkg.sandbox(; artifacts=false, destination=PKG_EXPORT_DIR)
# Create local registry
if !isdir(LOCAL_REGISTRY_PATH)
println("🗂️ Creating local registry [$LOCAL_REGISTRY_NAME]")
LocalRegistry.create_registry(
LOCAL_REGISTRY_NAME;
registry_path = LOCAL_REGISTRY_PATH,
description = "Airgapped Julia registry created on $(Dates.today())"
)
end
# Register packages
println("🔁 Registering exported packages")
for (root, _, files) in walkdir(PKG_EXPORT_DIR)
if "Project.toml" in files
try
LocalRegistry.register(path=root, registry=LOCAL_REGISTRY_PATH)
println("✅ Registered $(basename(root))")
catch e
@warn "⚠️ Failed to register $(root)" exception = e
end
end
end
# Remove default registry and use only local
Pkg.Registry.rm("General"; force=true)
Pkg.Registry.add(Pkg.RegistrySpec(path=LOCAL_REGISTRY_PATH))
# Final instantiate to verify
Pkg.instantiate()
Pkg.precompile()
# Copy Julia binary
println("🚚 Copying Julia binary from [$JULIA_BINARY_SOURCE] to [$JULIA_BINARY_DEST]")
run(`cp -r $JULIA_BINARY_SOURCE $JULIA_BINARY_DEST`)
# Create launch script
launcher = """
#!/bin/bash
DIR=\$(cd \$(dirname \$0) && pwd)
\${DIR}/julia/bin/julia --project=\${DIR}/AirgapProject -e '
using Pkg
Pkg.Registry.add(Pkg.RegistrySpec(path=joinpath(@__DIR__, "loca
"""
# then in shell on the airgapped machine
tar -xzf julia_airgap_bundle.tar.gz
./run_airgap.sh
I haven’t tested this, but it should be debuggable to get you to Julia-on-a-stick. If run in a clean environment to a clean device it should be safe from anything after theoretically possible malware packages in the registry. If feeling particularly cautious, use a dedicated device for each system to be installed to, so as not to pass around any germs that may be lurking from other vectors.