public void Update() { _processRunner.RunShell("apt-get update", new RunnerOptions { Env = new Dictionary <string, string> { { "APT_CONFIG", _aptDirectoryPrepService.AptConfigFile }, { "DEBIAN_FRONTEND", "noninteractive" }, { "DEBCONF_NONINTERACTIVE_SEEN", "true" }, { "LC_ALL", "C" }, { "LANGUAGE", "C" }, { "LANG", "C" } } }); }
public void GenerateRootFs(string directory, bool overwrite, bool runStage2) { if (!Env.IsRoot) { _logger.LogWarning("The currently running user is not root. Some commands will be run with sudo."); } _logger.LogInformation("Updating the package cache..."); _aptGetService.Update(); if (runStage2) { // Let's make sure arch-chroot is available. if (!File.Exists("/usr/bin/arch-chroot")) { _logger.LogError($"You indicated you wanted to run stage2, but arch-chroot isn't available. Try running {"sudo apt-get install arch-install-scripts".Quoted()}."); throw new Exception("The command arch-chroot isn't available."); } } var image = GetImage(); var imageLock = GetImageLock(); var preseedFiles = GetPreseeds(image); var scripts = GetScripts(image); if (string.IsNullOrEmpty(directory)) { directory = "rootfs"; } if (!Path.IsPathRooted(directory)) { directory = Path.Combine(Directory.GetCurrentDirectory(), directory); } directory = Path.GetFullPath(directory); _logger.LogInformation("Generating rootfs at {directory}.", directory); _logger.LogInformation("Cleaning directory..."); if (overwrite) { if (Directory.Exists(directory)) { _logger.LogInformation("Directory exists, removing..."); _processRunner.RunShell($"rm -rf {directory.Quoted()}", new RunnerOptions { UseSudo = !Env.IsRoot }); } _processRunner.RunShell($"mkdir {directory.Quoted()}", new RunnerOptions { UseSudo = !Env.IsRoot }); } else { if (!Directory.Exists(directory)) { _logger.LogInformation("Creating directory.."); _processRunner.RunShell($"mkdir {directory.Quoted()}", new RunnerOptions { UseSudo = !Env.IsRoot }); } else { // Make sure it is empty. if (Directory.GetFiles(directory).Length > 0 || Directory.GetDirectories(directory, "*").Length > 0) { throw new Exception("The directory is not empty."); } } } _logger.LogInformation("Creating required folders/files..."); _processRunner.RunShell("mkdir -p \"var/lib/dpkg/info\"", new RunnerOptions { UseSudo = !Env.IsRoot, WorkingDirectory = directory }); _processRunner.RunShell("touch \"var/lib/dpkg/available\"", new RunnerOptions { UseSudo = !Env.IsRoot, WorkingDirectory = directory }); _processRunner.RunShell("touch \"var/lib/dpkg/status\"", new RunnerOptions { UseSudo = !Env.IsRoot, WorkingDirectory = directory }); _processRunner.RunShell("mkdir -p \"var/lib/dpkg/updates\"", new RunnerOptions { UseSudo = !Env.IsRoot, WorkingDirectory = directory }); _processRunner.RunShell("mkdir -p \"etc/apt\"", new RunnerOptions { UseSudo = !Env.IsRoot, WorkingDirectory = directory }); _processRunner.RunShell("mkdir -p \"etc/apt\"", new RunnerOptions { UseSudo = !Env.IsRoot, WorkingDirectory = directory }); _processRunner.RunShell("mkdir -p \"stage2/debs\"", new RunnerOptions { UseSudo = !Env.IsRoot, WorkingDirectory = directory }); _processRunner.RunShell("mkdir -p \"stage2/preseeds\"", new RunnerOptions { UseSudo = !Env.IsRoot, WorkingDirectory = directory }); _logger.LogInformation("Including apt repositories..."); foreach (var repo in GetRepositories(image)) { _processRunner.RunShell($"echo {repo.ToString().Quoted()} | tee -a ./etc/apt/sources.list", new RunnerOptions { UseSudo = !Env.IsRoot, WorkingDirectory = directory }); } // Download the packages. _logger.LogInformation("Downloading the debs..."); var debFolder = Path.Combine(_workspaceConfig.RootDirectory, ".debs"); debFolder.EnsureDirectoryExists(); _aptGetService.Download(imageLock.InstalledPackages.ToDictionary(x => x.Key, x => x.Value.Version), debFolder); // Debs will go in here to reinstall in stage 2. var stage2DebDirectory = Path.Combine(directory, "stage2", "debs"); var stage2Script = new StringBuilder(); stage2Script.AppendLine("#!/bin/sh"); stage2Script.AppendLine("set -e"); stage2Script.AppendLine("export DEBIAN_FRONTEND=noninteractive"); stage2Script.AppendLine("export DEBCONF_NONINTERACTIVE_SEEN=true "); stage2Script.AppendLine("export LC_ALL=C"); stage2Script.AppendLine("export LANGUAGE=C"); stage2Script.AppendLine("export LANG=C"); stage2Script.AppendLine($"export APT_TOOL_ROOTFS_NATIVE_DIR={directory.Quoted()}"); // Step 1, run all the preseeds. foreach (var preseedFile in preseedFiles) { var fileName = Path.GetFileName(preseedFile) + Guid.NewGuid().ToString().Replace("-", ""); _processRunner.RunShell($"cp {preseedFile.Quoted()} {$"stage2/preseeds/{fileName}".Quoted()}", new RunnerOptions { UseSudo = !Env.IsRoot, WorkingDirectory = directory }); stage2Script.AppendLine($"debconf-set-selections {$"/stage2/preseeds/{fileName}".Quoted()}"); } foreach (var package in imageLock.InstalledPackages.Keys) { var installedPackage = imageLock.InstalledPackages[package]; var debFile = Path.Combine(debFolder, $"{package}_{installedPackage.Version.Version.Replace(":", "%3a")}_{installedPackage.Version.Architecture}.deb"); if (!File.Exists(debFile)) { throw new Exception($"The deb file {debFile} doesn't exist."); } _logger.LogInformation("Extracting: {package}", imageLock.InstalledPackages[package].Version.ToCommandParameter(package)); // Step 2, we extract the entire contents of the of the deb package. // This won't actually configure the package as installed, but it // will at least allow out chroot to execute commands (dpkg, etc). _dpkgService.Extract(debFile, directory, !Env.IsRoot); // Step 3, move the deb file into the rootfs for a stage2 setup. // In here, we will simply unpack the dpkg file, but not configure // it (only execute preinst). _processRunner.RunShell($"cp \"{debFile}\" \"{stage2DebDirectory}\"", new RunnerOptions { UseSudo = !Env.IsRoot }); stage2Script.AppendLine($"dpkg --unpack --force-confnew --force-overwrite --force-depends /stage2/debs/{Path.GetFileName(debFile)}"); } // Step 4, within the stage2, now we configure all the packages that we have unpacked. stage2Script.AppendLine("dpkg --configure -a"); if (scripts.Count >= 0) { _logger.LogInformation("Including the custom scripts in the stage2..."); var destinationDirectory = Path.Combine(directory, "stage2", "scripts"); _processRunner.RunShell($"mkdir -p {destinationDirectory.Quoted()}", new RunnerOptions { UseSudo = !Env.IsRoot }); foreach (var scriptLookup in scripts.ToLookup(x => x.Directory)) { var destinationScriptDirectoryName = $"{Path.GetFileName(scriptLookup.Key)}-{Guid.NewGuid().ToString().Replace("-", "")}"; var destinationScriptDirectory = Path.Combine(destinationDirectory, destinationScriptDirectoryName); _processRunner.RunShell($"mkdir {destinationScriptDirectory}", new RunnerOptions { UseSudo = !Env.IsRoot }); _processRunner.RunShell($"cp -ra {scriptLookup.Key}/* {destinationScriptDirectory}/", new RunnerOptions { UseSudo = !Env.IsRoot }); foreach (var script in scriptLookup) { stage2Script.AppendLine( $"bash -c \"cd /stage2/scripts/{destinationScriptDirectoryName} && ./{script.Name}\""); } } } if (!runStage2) // Done put this message if we will be deleting this ourselves. { stage2Script.AppendLine("echo \"DONE! Don't forget to delete /stage2!!\""); } _logger.LogInformation("Saving stage2 script to be run via chroot: {script}", "/stage2/stage2.sh"); var stage2ScriptPath = Path.Combine(directory, "stage2", "stage2.sh"); var tempPath = Path.GetTempFileName(); try { File.WriteAllText(tempPath, stage2Script.ToString()); _processRunner.RunShell($"cp \"{tempPath}\" \"{stage2ScriptPath}\"", new RunnerOptions { UseSudo = !Env.IsRoot }); } finally { File.Delete(tempPath); } _processRunner.RunShell($"chmod +x {stage2ScriptPath}", new RunnerOptions { UseSudo = !Env.IsRoot }); if (runStage2) { _logger.LogInformation("Running stage2..."); _processRunner.RunShell($"arch-chroot {directory.Quoted()} /stage2/stage2.sh", new RunnerOptions { UseSudo = !Env.IsRoot }); _processRunner.RunShell($"rm -r {Path.Combine(directory, "stage2").Quoted()}", new RunnerOptions { UseSudo = !Env.IsRoot }); } _logger.LogInformation("Done!"); }