package main import ( "bufio" "bytes" "compress/gzip" "crypto/md5" "crypto/sha1" "crypto/sha256" "fmt" "io" "log" "os" "os/exec" "path/filepath" "strings" "time" ) // --- Configuration --- // !!! IMPORTANT: Set your GPG Key ID here! // Find it with: gpg --list-secret-keys --keyid-format=long const gpgKeyID = "YOUR_GPG_KEY_ID" const dist = "sid" const component = "main" const poolDir = "pool" const distsDir = "dists" var architectures = []string{"amd64", "riscv64", "all"} // DebInfo holds the control information for a single .deb package. type DebInfo struct { ControlData map[string]string Filename string Size int64 MD5Sum string SHA1Sum string SHA256Sum string } func makeDistroFiles() { log.Println("--- Starting Debian repository generation in Go ---") if gpgKeyID == "YOUR_GPG_KEY_ID" || gpgKeyID == "" { log.Fatal("ERROR: Please set the 'gpgKeyID' constant at the top of the script.") } // 1. Clean and create directory structure distPath := filepath.Join(distsDir, dist) log.Printf("Cleaning up old dists directory: %s", distPath) if err := os.RemoveAll(distPath); err != nil { log.Fatalf("Failed to remove old dists directory: %v", err) } log.Println("Creating new directory structure...") for _, arch := range architectures { binPath := filepath.Join(distPath, component, "binary-"+arch) if err := os.MkdirAll(binPath, 0755); err != nil { log.Fatalf("Failed to create directory %s: %v", binPath, err) } } // 2. Scan pool directory for .deb files and gather info log.Printf("Scanning for .deb files in %s/ જુ", poolDir) debInfos, err := scanDebs(poolDir) if err != nil { log.Fatalf("Failed to scan .deb files: %v", err) } log.Printf("Found %d total .deb packages.", len(debInfos)) // 3. Group packages by architecture debsByArch := make(map[string][]DebInfo) for _, deb := range debInfos { arch := deb.ControlData["Architecture"] debsByArch[arch] = append(debsByArch[arch], deb) } // Add the 'all' packages to each specific architecture list as well, as is standard. for _, arch := range architectures { if arch != "all" { debsByArch[arch] = append(debsByArch[arch], debsByArch["all"]...) } } // 4. Generate Packages files log.Println("Generating Packages files...") for _, arch := range architectures { binPath := filepath.Join(distPath, component, "binary-"+arch) packagesFile := filepath.Join(binPath, "Packages") var content strings.Builder for _, deb := range debsByArch[arch] { for key, val := range deb.ControlData { fmt.Fprintf(&content, "%s: %s\n", key, val) } fmt.Fprintf(&content, "Filename: %s\n", deb.Filename) fmt.Fprintf(&content, "Size: %d\n", deb.Size) fmt.Fprintf(&content, "MD5sum: %s\n", deb.MD5Sum) fmt.Fprintf(&content, "SHA1: %s\n", deb.SHA1Sum) fmt.Fprintf(&content, "SHA256: %s\n", deb.SHA256Sum) fmt.Fprintln(&content) } if err := os.WriteFile(packagesFile, []byte(content.String()), 0644); err != nil { log.Fatalf("Failed to write Packages file for %s: %v", arch, err) } // Compress the Packages file if err := compressFile(packagesFile, "gz"); err != nil { log.Fatalf("Failed to gzip Packages file for %s: %v", arch, err) } if err := compressFile(packagesFile, "bz2"); err != nil { log.Fatalf("Failed to bzip2 Packages file for %s: %v", arch, err) } } // 5. Generate and sign the Release file log.Println("Generating and signing Release file...") if err := generateAndSignReleaseFile(distPath); err != nil { log.Fatalf("Failed to generate or sign Release file: %v", err) } log.Println("--- Repository generation complete! ---") } // scanDebs finds all .deb files and extracts their metadata. func scanDebs(root string) ([]DebInfo, error) { var debs []DebInfo err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() && strings.HasSuffix(info.Name(), ".deb") { log.Printf(" -> Processing %s", path) // Get control info cmd := exec.Command("dpkg-deb", "-I", path) var out bytes.Buffer cmd.Stdout = &out if err := cmd.Run(); err != nil { return fmt.Errorf("failed to run dpkg-deb on %s: %v", path, err) } controlData := parseControlData(out.String()) // Get checksums md5sum, sha1sum, sha256sum, err := getChecksums(path) if err != nil { return err } relativePath, err := filepath.Rel(".", path) if err != nil { return err } debs = append(debs, DebInfo{ ControlData: controlData, Filename: relativePath, Size: info.Size(), MD5Sum: md5sum, SHA1Sum: sha1sum, SHA256Sum: sha256sum, }) } return nil }) return debs, err } // parseControlData converts the text output of dpkg-deb into a map. func parseControlData(data string) map[string]string { control := make(map[string]string) scanner := bufio.NewScanner(strings.NewReader(data)) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) parts := strings.SplitN(line, ":", 2) if len(parts) == 2 { control[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1]) } } return control } // getChecksums calculates all required hashes for a file. // FIX 1: Renamed return variables to avoid shadowing package names. func getChecksums(filePath string) (md5sum, sha1sum, sha256sum string, err error) { file, err := os.Open(filePath) if err != nil { return "", "", "", err } defer file.Close() hMD5 := md5.New() hSHA1 := sha1.New() hSHA256 := sha256.New() // TeeReader allows writing to multiple hashers at once multiWriter := io.MultiWriter(hMD5, hSHA1, hSHA256) if _, err := io.Copy(multiWriter, file); err != nil { return "", "", "", err } return fmt.Sprintf("%x", hMD5.Sum(nil)), fmt.Sprintf("%x", hSHA1.Sum(nil)), fmt.Sprintf("%x", hSHA256.Sum(nil)), nil } // compressFile creates a compressed version of a file (gz or bz2). func compressFile(sourcePath, format string) error { if format == "gz" { sourceFile, err := os.Open(sourcePath) if err != nil { return err } defer sourceFile.Close() destPath := sourcePath + ".gz" destFile, err := os.Create(destPath) if err != nil { return err } defer destFile.Close() writer := gzip.NewWriter(destFile) defer writer.Close() _, err = io.Copy(writer, sourceFile) return err } else if format == "bz2" { // FIX 2: Shell out to the bzip2 command for compression. destPath := sourcePath + ".bz2" cmd := exec.Command("bzip2", "-c", sourcePath) outfile, err := os.Create(destPath) if err != nil { return fmt.Errorf("failed to create destination file for bzip2: %v", err) } defer outfile.Close() cmd.Stdout = outfile return runCommand(cmd) } return fmt.Errorf("unsupported compression format: %s", format) } // generateAndSignReleaseFile creates the main Release file and signs it. func generateAndSignReleaseFile(distPath string) error { releasePath := filepath.Join(distPath, "Release") var content strings.Builder // Add headers fmt.Fprintf(&content, "Origin: Your Repository\n") fmt.Fprintf(&content, "Label: Your Repository\n") fmt.Fprintf(&content, "Suite: %s\n", dist) fmt.Fprintf(&content, "Codename: %s\n", dist) fmt.Fprintf(&content, "Date: %s\n", time.Now().UTC().Format(time.RFC1123Z)) fmt.Fprintf(&content, "Architectures: %s\n", strings.Join(architectures, " ")) fmt.Fprintf(&content, "Components: %s\n", component) fmt.Fprintf(&content, "Description: A cool repository\n") // Add checksums addChecksums := func(name string) { fmt.Fprintf(&content, "%s:\n", name) err := filepath.Walk(distPath, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() && info.Name() != "Release" && info.Name() != "InRelease" && info.Name() != "Release.gpg" { relativePath, _ := filepath.Rel(distPath, path) fileBytes, err := os.ReadFile(path) if err != nil { return err } var sum string switch name { case "MD5Sum": sum = fmt.Sprintf("%x", md5.Sum(fileBytes)) case "SHA1": sum = fmt.Sprintf("%x", sha1.Sum(fileBytes)) case "SHA256": // FIX 3: Use the correct sha256.Sum256 function. sum = fmt.Sprintf("%x", sha256.Sum256(fileBytes)) } fmt.Fprintf(&content, " %s %d %s\n", sum, info.Size(), relativePath) } return nil }) if err != nil { log.Printf("Warning: could not walk path for checksums: %v", err) } } addChecksums("MD5Sum") addChecksums("SHA1") addChecksums("SHA256") if err := os.WriteFile(releasePath, []byte(content.String()), 0644); err != nil { return fmt.Errorf("failed to write Release file: %v", err) } // Sign the file log.Println("Signing with GPG key:", gpgKeyID) // Create InRelease cmdClearSign := exec.Command("gpg", "--default-key", gpgKeyID, "--clearsign", "-o", filepath.Join(distPath, "InRelease"), releasePath) if err := runCommand(cmdClearSign); err != nil { return fmt.Errorf("failed to create InRelease: %v", err) } // Create Release.gpg cmdDetachedSign := exec.Command("gpg", "--default-key", gpgKeyID, "-abs", "-o", filepath.Join(distPath, "Release.gpg"), releasePath) if err := runCommand(cmdDetachedSign); err != nil { return fmt.Errorf("failed to create Release.gpg: %v", err) } return nil } func runCommand(cmd *exec.Cmd) error { var stderr bytes.Buffer cmd.Stderr = &stderr err := cmd.Run() if err != nil { return fmt.Errorf("command '%s' failed: %v\nStderr: %s", cmd.String(), err, stderr.String()) } return nil }