{"id":37134931,"url":"https://github.com/circlecloud/archiver","last_synced_at":"2026-01-14T15:44:36.530Z","repository":{"id":65134349,"uuid":"582594400","full_name":"circlecloud/archiver","owner":"circlecloud","description":"Easily create \u0026 extract archives, and compress \u0026 decompress files of various formats","archived":false,"fork":true,"pushed_at":"2022-12-28T01:24:15.000Z","size":483,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2024-06-20T10:14:12.521Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/mholt/archiver/v4","language":"Go","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"mholt/archiver","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/circlecloud.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":".github/SECURITY.md","support":null},"funding":{"github":["mholt"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2022-12-27T10:04:33.000Z","updated_at":"2024-06-20T10:14:12.521Z","dependencies_parsed_at":"2023-01-31T05:46:08.205Z","dependency_job_id":null,"html_url":"https://github.com/circlecloud/archiver","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/circlecloud/archiver","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/circlecloud%2Farchiver","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/circlecloud%2Farchiver/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/circlecloud%2Farchiver/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/circlecloud%2Farchiver/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/circlecloud","download_url":"https://codeload.github.com/circlecloud/archiver/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/circlecloud%2Farchiver/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28424601,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T15:24:48.085Z","status":"ssl_error","status_checked_at":"2026-01-14T15:23:41.940Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-14T15:44:35.880Z","updated_at":"2026-01-14T15:44:36.519Z","avatar_url":"https://github.com/circlecloud.png","language":"Go","funding_links":["https://github.com/sponsors/mholt"],"categories":[],"sub_categories":[],"readme":"# archiver [![Go Reference](https://pkg.go.dev/badge/github.com/mholt/archiver/v4.svg)](https://pkg.go.dev/github.com/mholt/archiver/v4) [![Ubuntu-latest](https://github.com/mholt/archiver/actions/workflows/ubuntu-latest.yml/badge.svg)](https://github.com/mholt/archiver/actions/workflows/ubuntu-latest.yml) [![Macos-latest](https://github.com/mholt/archiver/actions/workflows/macos-latest.yml/badge.svg)](https://github.com/mholt/archiver/actions/workflows/macos-latest.yml) [![Windows-latest](https://github.com/mholt/archiver/actions/workflows/windows-latest.yml/badge.svg)](https://github.com/mholt/archiver/actions/workflows/windows-latest.yml)\n\nIntroducing **Archiver 4.0** - a cross-platform, multi-format archive utility and Go library. A powerful and flexible library meets an elegant CLI in this generic replacement for several platform-specific or format-specific archive utilities.\n\n**:warning: v4 is in ALPHA. The core library APIs work pretty well but the command has not been implemented yet, nor have most automated tests. If you need the `arc` command, stick with v3 for now.**\n\n## Features\n\n- Stream-oriented APIs\n- Automatically identify archive and compression formats:\n\t- By file name\n\t- By header\n- Traverse directories, archive files, and any other file uniformly as [`io/fs`](https://pkg.go.dev/io/fs) file systems:\n\t- [`DirFS`](https://pkg.go.dev/github.com/mholt/archiver/v4#DirFS)\n\t- [`FileFS`](https://pkg.go.dev/github.com/mholt/archiver/v4#FileFS)\n\t- [`ArchiveFS`](https://pkg.go.dev/github.com/mholt/archiver/v4#ArchiveFS)\n- Compress and decompress files\n- Create and extract archive files\n- Walk or traverse into archive files\n- Extract only specific files from archives\n- Insert (append) into .tar files\n- Read from password-protected 7-Zip files\n- Numerous archive and compression formats supported\n- Extensible (add more formats just by registering them)\n- Cross-platform, static binary\n- Pure Go (no cgo)\n- Multithreaded Gzip\n- Adjust compression levels\n- Automatically add compressed files to zip archives without re-compressing\n- Open password-protected RAR archives\n\n### Supported compression formats\n\n- brotli (.br)\n- bzip2 (.bz2)\n- flate (.zip)\n- gzip (.gz)\n- lz4 (.lz4)\n- snappy (.sz)\n- xz (.xz)\n- zlib (.zz)\n- zstandard (.zst)\n\n### Supported archive formats\n\n- .zip\n- .tar (including any compressed variants like .tar.gz)\n- .rar (read-only)\n- .7z (read-only)\n\nTar files can optionally be compressed using any compression format.\n\n## Command use\n\nComing soon for v4. See [the last v3 docs](https://github.com/mholt/archiver/tree/v3.5.1).\n\n\n## Library use\n\n```bash\n$ go get github.com/mholt/archiver/v4\n```\n\n\n### Create archive\n\nCreating archives can be done entirely without needing a real disk or storage device since all you need is a list of [`File` structs](https://pkg.go.dev/github.com/mholt/archiver/v4#File) to pass in.\n\nHowever, creating archives from files on disk is very common, so you can use the [`FilesFromDisk()` function](https://pkg.go.dev/github.com/mholt/archiver/v4#FilesFromDisk) to help you map filenames on disk to their paths in the archive. Then create and customize the format type.\n\nIn this example, we add 4 files and a directory (which includes its contents recursively) to a .tar.gz file:\n\n```go\n// map files on disk to their paths in the archive\nfiles, err := archiver.FilesFromDisk(nil, map[string]string{\n\t\"/path/on/disk/file1.txt\": \"file1.txt\",\n\t\"/path/on/disk/file2.txt\": \"subfolder/file2.txt\",\n\t\"/path/on/disk/file3.txt\": \"\",              // put in root of archive as file3.txt\n\t\"/path/on/disk/file4.txt\": \"subfolder/\",    // put in subfolder as file4.txt\n\t\"/path/on/disk/folder\":    \"Custom Folder\", // contents added recursively\n})\nif err != nil {\n\treturn err\n}\n\n// create the output file we'll write to\nout, err := os.Create(\"example.tar.gz\")\nif err != nil {\n\treturn err\n}\ndefer out.Close()\n\n// we can use the CompressedArchive type to gzip a tarball\n// (compression is not required; you could use Tar directly)\nformat := archiver.CompressedArchive{\n\tCompression: archiver.Gz{},\n\tArchival:    archiver.Tar{},\n}\n\n// create the archive\nerr = format.Archive(context.Background(), out, files)\nif err != nil {\n\treturn err\n}\n```\n\nThe first parameter to `FilesFromDisk()` is an optional options struct, allowing you to customize how files are added.\n\n### Extract archive\n\nExtracting an archive, extracting _from_ an archive, and walking an archive are all the same function.\n\nSimply use your format type (e.g. `Zip`) to call `Extract()`. You'll pass in a context (for cancellation), the input stream, the list of files you want out of the archive, and a callback function to handle each file. \n\nIf you want all the files, pass in a nil list of file paths.\n\n```go\n// the type that will be used to read the input stream\nformat := archiver.Zip{}\n\n// the list of files we want out of the archive; any\n// directories will include all their contents unless\n// we return fs.SkipDir from our handler\n// (leave this nil to walk ALL files from the archive)\nfileList := []string{\"file1.txt\", \"subfolder\"}\n\nhandler := func(ctx context.Context, f archiver.File) error {\n\t// do something with the file\n\treturn nil\n}\n\nerr := format.Extract(ctx, input, fileList, handler)\nif err != nil {\n\treturn err\n}\n```\n\n### Identifying formats\n\nHave an input stream with unknown contents? No problem, archiver can identify it for you. It will try matching based on filename and/or the header (which peeks at the stream):\n\n```go\nformat, input, err := archiver.Identify(\"filename.tar.zst\", input)\nif err != nil {\n\treturn err\n}\n// you can now type-assert format to whatever you need;\n// be sure to use returned stream to re-read consumed bytes during Identify()\n\n// want to extract something?\nif ex, ok := format.(archiver.Extractor); ok {\n\t// ... proceed to extract\n}\n\n// or maybe it's compressed and you want to decompress it?\nif decom, ok := format.(archiver.Decompressor); ok {\n\trc, err := decom.OpenReader(unknownFile)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer rc.Close()\n\n\t// read from rc to get decompressed data\n}\n```\n\n`Identify()` works by reading an arbitrary number of bytes from the beginning of the stream (just enough to check for file headers). It buffers them and returns a new reader that lets you re-read them anew.\n\n### Virtual file systems\n\nThis is my favorite feature.\n\nLet's say you have a file. It could be a real directory on disk, an archive, a compressed archive, or any other regular file. You don't really care; you just want to use it uniformly no matter what it is.\n\nUse archiver to simply create a file system:\n\n```go\n// filename could be:\n// - a folder (\"/home/you/Desktop\")\n// - an archive (\"example.zip\")\n// - a compressed archive (\"example.tar.gz\")\n// - a regular file (\"example.txt\")\n// - a compressed regular file (\"example.txt.gz\")\nfsys, err := archiver.FileSystem(filename)\nif err != nil {\n\treturn err\n}\n```\n\nThis is a fully-featured `fs.FS`, so you can open files and read directories, no matter what kind of file the input was.\n\nFor example, to open a specific file:\n\n```go\nf, err := fsys.Open(\"file\")\nif err != nil {\n\treturn err\n}\ndefer f.Close()\n```\n\nIf you opened a regular file, you can read from it. If it's a compressed file, reads are automatically decompressed.\n\nIf you opened a directory, you can list its contents:\n\n```go\nif dir, ok := f.(fs.ReadDirFile); ok {\n\t// 0 gets all entries, but you can pass \u003e 0 to paginate\n\tentries, err := dir.ReadDir(0)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, e := range entries {\n\t\tfmt.Println(e.Name())\n\t}\n}\n```\n\nOr get a directory listing this way:\n\n```go\nentries, err := fsys.ReadDir(\"Playlists\")\nif err != nil {\n\treturn err\n}\nfor _, e := range entries {\n\tfmt.Println(e.Name())\n}\n```\n\nOr maybe you want to walk all or part of the file system, but skip a folder named `.git`:\n\n```go\nerr := fs.WalkDir(fsys, \".\", func(path string, d fs.DirEntry, err error) error {\n\tif err != nil {\n\t\treturn err\n\t}\n\tif path == \".git\" {\n\t\treturn fs.SkipDir\n\t}\n\tfmt.Println(\"Walking:\", path, \"Dir?\", d.IsDir())\n\treturn nil\n})\nif err != nil {\n\treturn err\n}\n```\n\n#### Use with `http.FileServer`\n\nIt can be used with http.FileServer to browse archives and directories in a browser. However, due to how http.FileServer works, don't directly use http.FileServer with compressed files; instead wrap it like following:\n\n```go\nfileServer := http.FileServer(http.FS(archiveFS))\nhttp.HandleFunc(\"/\", func(writer http.ResponseWriter, request *http.Request) {\n\t// disable range request\n\twriter.Header().Set(\"Accept-Ranges\", \"none\")\n\trequest.Header.Del(\"Range\")\n\t\n\t// disable content-type sniffing\n\tctype := mime.TypeByExtension(filepath.Ext(request.URL.Path))\n\twriter.Header()[\"Content-Type\"] = nil\n\tif ctype != \"\" {\n\t\twriter.Header().Set(\"Content-Type\", ctype)\n\t}\n\tfileServer.ServeHTTP(writer, request)\n})\n```\n\nhttp.FileServer will try to sniff the Content-Type by default if it can't be inferred from file name. To do this, the http package will try to read from the file and then Seek back to file start, which the libray can't achieve currently. The same goes with Range requests. Seeking in archives is not currently supported by archiver due to limitations in dependencies.\n\nIf content-type is desirable, you can [register it](https://pkg.go.dev/mime#AddExtensionType) yourself.\n\n### Compress data\n\nCompression formats let you open writers to compress data:\n\n```go\n// wrap underlying writer w\ncompressor, err := archiver.Zstd{}.OpenWriter(w)\nif err != nil {\n\treturn err\n}\ndefer compressor.Close()\n\n// writes to compressor will be compressed\n```\n\n### Decompress data\n\nSimilarly, compression formats let you open readers to decompress data:\n\n```go\n// wrap underlying reader r\ndecompressor, err := archiver.Brotli{}.OpenReader(r)\nif err != nil {\n\treturn err\n}\ndefer decompressor.Close()\n\n// reads from decompressor will be decompressed\n```\n\n### Append to tarball\n\nTar archives can be appended to without creating a whole new archive by calling `Insert()` on a tar stream. However, this requires that the tarball is not compressed (due to complexities with modifying compression dictionaries).\n\nHere is an example that appends a file to a tarball on disk:\n\n```go\ntarball, err := os.OpenFile(\"example.tar\", os.O_RDWR, 0644)\nif err != nil {\n\treturn err\n}\ndefer tarball.Close()\n\n// prepare a text file for the root of the archive\nfiles, err := archiver.FilesFromDisk(nil, map[string]string{\n\t\"/home/you/lastminute.txt\": \"\",\n})\n\nerr := archiver.Tar{}.Insert(context.Background(), tarball, files)\nif err != nil {\n\treturn err\n}\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcirclecloud%2Farchiver","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcirclecloud%2Farchiver","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcirclecloud%2Farchiver/lists"}