Compare commits

...

4 Commits

Author SHA1 Message Date
Wolfgang Bumiller
286c55d5b4 bump proxmox-apt to 0.9.4-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-06-09 11:05:20 +02:00
Fiona Ebner
6ec35bdbbd apt: repositories: also detect repository with next suite as configured
This avoids that no standard Proxmox repository can be detected during
upgrade anymore. There is a 'ignore-pre-upgrade-warning' about the
suite already, that the frontend can display when upgrading is not
allowed yet.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
2023-06-09 10:46:04 +02:00
Wolfgang Bumiller
82417de8a8 import proxmox-apt crate
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-05-24 08:39:45 +02:00
Wolfgang Bumiller
cc553060e0 import proxmox-openid crate
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-05-24 08:12:09 +02:00
66 changed files with 1150500 additions and 0 deletions

View File

@ -1,6 +1,7 @@
[workspace]
members = [
"proxmox-api-macro",
"proxmox-apt",
"proxmox-async",
"proxmox-auth-api",
"proxmox-borrow",
@ -11,6 +12,7 @@ members = [
"proxmox-ldap",
"proxmox-login",
"proxmox-metrics",
"proxmox-openid",
"proxmox-rest-server",
"proxmox-router",
"proxmox-schema",
@ -33,6 +35,7 @@ edition = "2021"
license = "AGPL-3"
repository = "https://git.proxmox.com/?p=proxmox.git"
exclude = [ "debian" ]
homepage = "https://www.proxmox.com"
[workspace.dependencies]
# any features enabled here are enabled on all members using 'workspace = true'!

23
proxmox-apt/Cargo.toml Normal file
View File

@ -0,0 +1,23 @@
[package]
name = "proxmox-apt"
version = "0.9.4"
description = "Proxmox library for APT"
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
exclude = [ "debian" ]
[dependencies]
anyhow.workspace = true
hex.workspace = true
once_cell.workspace = true
openssl.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
rfc822-like = "0.2.1"
proxmox-schema = { workspace = true, features = [ "api-macro" ] }

View File

@ -0,0 +1,121 @@
rust-proxmox-apt (0.9.4-1) bullseye; urgency=medium
* repositories: also detect repository with next suite as configure
-- Proxmox Support Team <support@proxmox.com> Fri, 09 Jun 2023 10:47:41 +0200
rust-proxmox-apt (0.9.3-1) stable; urgency=medium
* packages file: add section field
* deb822: source index support
-- Proxmox Support Team <support@proxmox.com> Wed, 19 Oct 2022 16:17:11 +0200
rust-proxmox-apt (0.9.2-1) stable; urgency=medium
* release: add Commands file reference type
* release: add 'architecture' helper
* release: fix typo in 'Acquire-By-Hash'
-- Proxmox Support Team <support@proxmox.com> Fri, 16 Sep 2022 14:17:10 +0200
rust-proxmox-apt (0.9.1-1) stable; urgency=medium
* release-file: improve invalid file-reference handling
* add ceph quincy repositories
-- Proxmox Support Team <support@proxmox.com> Tue, 6 Sep 2022 10:33:17 +0200
rust-proxmox-apt (0.9.0-1) stable; urgency=medium
* AptRepositoryFile: make path optional
-- Proxmox Support Team <support@proxmox.com> Thu, 21 Jul 2022 13:25:20 +0200
rust-proxmox-apt (0.8.1-1) stable; urgency=medium
* upgrade to 2021 edition
* check suites: add special check for Debian security repository
* file: add pre-parsed content variant
* add module for parsing Packages and Release (deb822 like) files
-- Proxmox Support Team <support@proxmox.com> Thu, 21 Jul 2022 12:08:23 +0200
rust-proxmox-apt (0.8.0-1) stable; urgency=medium
* update to proxox-schema crate
-- Proxmox Support Team <support@proxmox.com> Fri, 08 Oct 2021 11:55:47 +0200
rust-proxmox-apt (0.7.0-1) stable; urgency=medium
* update to proxmox 0.13.0
-- Proxmox Support Team <support@proxmox.com> Tue, 24 Aug 2021 15:38:52 +0200
rust-proxmox-apt (0.6.0-1) stable; urgency=medium
* standard repos: add suite parameter for stricter detection
* check repos: have caller specify the current suite
* add type DebianCodename
-- Proxmox Support Team <support@proxmox.com> Thu, 29 Jul 2021 18:06:54 +0200
rust-proxmox-apt (0.5.1-1) stable; urgency=medium
* depend on proxmox 0.12.0
-- Proxmox Support Team <support@proxmox.com> Tue, 20 Jul 2021 13:18:02 +0200
rust-proxmox-apt (0.5.0-1) stable; urgency=medium
* standard repo detection: handle alternative URI for PVE repos
-- Proxmox Support Team <support@proxmox.com> Fri, 16 Jul 2021 16:19:06 +0200
rust-proxmox-apt (0.4.0-1) stable; urgency=medium
* support quote-word parsing for one-line format
* avoid backtick unicode symbol in string
-- Proxmox Support Team <support@proxmox.com> Thu, 01 Jul 2021 18:33:12 +0200
rust-proxmox-apt (0.3.1-1) stable; urgency=medium
* standard repos: allow conversion from handle and improve information
-- Proxmox Support Team <support@proxmox.com> Wed, 30 Jun 2021 20:42:52 +0200
rust-proxmox-apt (0.3.0-1) stable; urgency=medium
* add get_cached_origin method and an initial config module
* check: return 'origin' property instead of 'badge' for official host
* standard repos: drop product acronym from repo name
-- Proxmox Support Team <support@proxmox.com> Wed, 30 Jun 2021 13:29:13 +0200
rust-proxmox-apt (0.2.0-1) stable; urgency=medium
* Add functions to check repositories.
* Add handling of standard Proxmox repositories.
-- Proxmox Support Team <support@proxmox.com> Wed, 23 Jun 2021 14:57:52 +0200
rust-proxmox-apt (0.1.0-1) stable; urgency=medium
* Initial release.
-- Proxmox Support Team <support@proxmox.com> Thu, 18 Feb 2021 10:20:44 +0100

View File

@ -0,0 +1,52 @@
Source: rust-proxmox-apt
Section: rust
Priority: optional
Build-Depends: debhelper (>= 12),
dh-cargo (>= 25),
cargo:native <!nocheck>,
rustc:native <!nocheck>,
libstd-rust-dev <!nocheck>,
librust-anyhow-1+default-dev <!nocheck>,
librust-hex-0.4+default-dev <!nocheck>,
librust-once-cell-1+default-dev (>= 1.3.1-~~) <!nocheck>,
librust-openssl-0.10+default-dev <!nocheck>,
librust-proxmox-schema-1+api-macro-dev (>= 1.3.7-~~) <!nocheck>,
librust-proxmox-schema-1+default-dev (>= 1.3.7-~~) <!nocheck>,
librust-rfc822-like-0.2+default-dev (>= 0.2.1-~~) <!nocheck>,
librust-serde-1+default-dev <!nocheck>,
librust-serde-1+derive-dev <!nocheck>,
librust-serde-json-1+default-dev <!nocheck>
Maintainer: Proxmox Support Team <support@proxmox.com>
Standards-Version: 4.6.1
Vcs-Git: git://git.proxmox.com/git/proxmox-apt.git
Vcs-Browser: https://git.proxmox.com/?p=proxmox-apt.git
Homepage: https://www.proxmox.com
X-Cargo-Crate: proxmox-apt
Rules-Requires-Root: no
Package: librust-proxmox-apt-dev
Architecture: any
Multi-Arch: same
Depends:
${misc:Depends},
librust-anyhow-1+default-dev,
librust-hex-0.4+default-dev,
librust-once-cell-1+default-dev (>= 1.3.1-~~),
librust-openssl-0.10+default-dev,
librust-proxmox-schema-1+api-macro-dev (>= 1.3.7-~~),
librust-proxmox-schema-1+default-dev (>= 1.3.7-~~),
librust-rfc822-like-0.2+default-dev (>= 0.2.1-~~),
librust-serde-1+default-dev,
librust-serde-1+derive-dev,
librust-serde-json-1+default-dev
Provides:
librust-proxmox-apt+default-dev (= ${binary:Version}),
librust-proxmox-apt-0-dev (= ${binary:Version}),
librust-proxmox-apt-0+default-dev (= ${binary:Version}),
librust-proxmox-apt-0.9-dev (= ${binary:Version}),
librust-proxmox-apt-0.9+default-dev (= ${binary:Version}),
librust-proxmox-apt-0.9.3-dev (= ${binary:Version}),
librust-proxmox-apt-0.9.3+default-dev (= ${binary:Version})
Description: Proxmox library for APT - Rust source code
This package contains the source for the Rust proxmox-apt crate, packaged by
debcargo for use with cargo and dh-cargo.

View File

@ -0,0 +1,18 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Files:
*
Copyright: 2019 - 2023 Proxmox Server Solutions GmbH <support@proxmox.com>
License: AGPL-3.0-or-later
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option) any
later version.
.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
details.
.
You should have received a copy of the GNU Affero General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>.

View File

@ -0,0 +1,7 @@
overlay = "."
crate_src_path = ".."
maintainer = "Proxmox Support Team <support@proxmox.com>"
[source]
vcs_git = "git://git.proxmox.com/git/proxmox-apt.git"
vcs_browser = "https://git.proxmox.com/?p=proxmox-apt.git"

35
proxmox-apt/src/config.rs Normal file
View File

@ -0,0 +1,35 @@
use once_cell::sync::OnceCell;
static GLOBAL_CONFIG: OnceCell<APTConfig> = OnceCell::new();
/// APT configuration variables.
pub struct APTConfig {
/// Dir::State
pub dir_state: String,
/// Dir::State::Lists
pub dir_state_lists: String,
}
impl APTConfig {
/// Create a new configuration overriding the provided values.
pub fn new(dir_state: Option<&str>, dir_state_lists: Option<&str>) -> Self {
Self {
dir_state: dir_state.unwrap_or("/var/lib/apt/").to_string(),
dir_state_lists: dir_state_lists.unwrap_or("lists/").to_string(),
}
}
}
/// Get the configuration.
///
/// Initializes with default values if init() wasn't called before.
pub fn get() -> &'static APTConfig {
GLOBAL_CONFIG.get_or_init(|| APTConfig::new(None, None))
}
/// Initialize the configuration.
///
/// Only has an effect if no init() or get() has been called yet.
pub fn init(config: APTConfig) -> &'static APTConfig {
GLOBAL_CONFIG.get_or_init(|| config)
}

View File

@ -0,0 +1,75 @@
mod release_file;
use anyhow::{bail, Error};
pub use release_file::{CompressionType, FileReference, FileReferenceType, ReleaseFile};
mod packages_file;
pub use packages_file::PackagesFile;
mod sources_file;
pub use sources_file::SourcesFile;
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct CheckSums {
pub md5: Option<[u8; 16]>,
pub sha1: Option<[u8; 20]>,
pub sha256: Option<[u8; 32]>,
pub sha512: Option<[u8; 64]>,
}
macro_rules! assert_hash_equality {
($l:expr, $r:expr) => {{
if $l != $r {
bail!("hash mismatch: {} != {}", hex::encode($l), hex::encode($r));
}
}};
}
impl CheckSums {
pub fn is_secure(&self) -> bool {
self.sha256.is_some() || self.sha512.is_some()
}
pub fn verify(&self, input: &[u8]) -> Result<(), Error> {
if !self.is_secure() {
bail!("No SHA256/SHA512 checksum specified.");
}
if let Some(expected) = self.sha512 {
let digest = openssl::sha::sha512(input);
assert_hash_equality!(digest, expected);
Ok(())
} else if let Some(expected) = self.sha256 {
let digest = openssl::sha::sha256(input);
assert_hash_equality!(digest, expected);
Ok(())
} else {
bail!("No trusted checksum found - verification not possible.");
}
}
/// Merge two instances of `CheckSums`.
pub fn merge(&mut self, rhs: &CheckSums) -> Result<(), Error> {
match (self.sha512, rhs.sha512) {
(_, None) => {}
(None, Some(sha512)) => self.sha512 = Some(sha512),
(Some(left), Some(right)) => assert_hash_equality!(left, right),
};
match (self.sha256, rhs.sha256) {
(_, None) => {}
(None, Some(sha256)) => self.sha256 = Some(sha256),
(Some(left), Some(right)) => assert_hash_equality!(left, right),
};
match (self.sha1, rhs.sha1) {
(_, None) => {}
(None, Some(sha1)) => self.sha1 = Some(sha1),
(Some(left), Some(right)) => assert_hash_equality!(left, right),
};
match (self.md5, rhs.md5) {
(_, None) => {}
(None, Some(md5)) => self.md5 = Some(md5),
(Some(left), Some(right)) => assert_hash_equality!(left, right),
};
Ok(())
}
}

View File

@ -0,0 +1,171 @@
use std::collections::HashMap;
use anyhow::{bail, Error};
use rfc822_like::de::Deserializer;
use serde::Deserialize;
use serde_json::Value;
use super::CheckSums;
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct PackagesFileRaw {
pub package: String,
pub source: Option<String>,
pub version: String,
pub section: String,
pub priority: String,
pub architecture: String,
pub essential: Option<String>,
pub depends: Option<String>,
pub recommends: Option<String>,
pub suggests: Option<String>,
pub breaks: Option<String>,
pub conflicts: Option<String>,
#[serde(rename = "Installed-Size")]
pub installed_size: Option<String>,
pub maintainer: String,
pub description: String,
pub filename: String,
pub size: String,
#[serde(rename = "Multi-Arch")]
pub multi_arch: Option<String>,
#[serde(rename = "MD5sum")]
pub md5_sum: Option<String>,
#[serde(rename = "SHA1")]
pub sha1: Option<String>,
#[serde(rename = "SHA256")]
pub sha256: Option<String>,
#[serde(rename = "SHA512")]
pub sha512: Option<String>,
#[serde(rename = "Description-md5")]
pub description_md5: Option<String>,
#[serde(flatten)]
pub extra_fields: HashMap<String, Value>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct PackageEntry {
pub package: String,
pub source: Option<String>,
pub version: String,
pub architecture: String,
pub file: String,
pub size: usize,
pub installed_size: Option<usize>,
pub checksums: CheckSums,
pub section: String,
}
#[derive(Debug, Default, PartialEq, Eq)]
/// A parsed representation of a Release file
pub struct PackagesFile {
pub files: Vec<PackageEntry>,
}
impl TryFrom<PackagesFileRaw> for PackageEntry {
type Error = Error;
fn try_from(value: PackagesFileRaw) -> Result<Self, Self::Error> {
let installed_size = match value.installed_size {
Some(val) => Some(val.parse::<usize>()?),
None => None,
};
let mut parsed = PackageEntry {
package: value.package,
source: value.source,
version: value.version,
architecture: value.architecture,
file: value.filename,
size: value.size.parse::<usize>()?,
installed_size,
checksums: CheckSums::default(),
section: value.section,
};
if let Some(md5) = value.md5_sum {
let mut bytes = [0u8; 16];
hex::decode_to_slice(md5, &mut bytes)?;
parsed.checksums.md5 = Some(bytes);
};
if let Some(sha1) = value.sha1 {
let mut bytes = [0u8; 20];
hex::decode_to_slice(sha1, &mut bytes)?;
parsed.checksums.sha1 = Some(bytes);
};
if let Some(sha256) = value.sha256 {
let mut bytes = [0u8; 32];
hex::decode_to_slice(sha256, &mut bytes)?;
parsed.checksums.sha256 = Some(bytes);
};
if let Some(sha512) = value.sha512 {
let mut bytes = [0u8; 64];
hex::decode_to_slice(sha512, &mut bytes)?;
parsed.checksums.sha512 = Some(bytes);
};
if !parsed.checksums.is_secure() {
bail!(
"no strong checksum found for package entry '{}'",
parsed.package
);
}
Ok(parsed)
}
}
impl TryFrom<String> for PackagesFile {
type Error = Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
value.as_bytes().try_into()
}
}
impl TryFrom<&[u8]> for PackagesFile {
type Error = Error;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let deserialized = <Vec<PackagesFileRaw>>::deserialize(Deserializer::new(value))?;
deserialized.try_into()
}
}
impl TryFrom<Vec<PackagesFileRaw>> for PackagesFile {
type Error = Error;
fn try_from(value: Vec<PackagesFileRaw>) -> Result<Self, Self::Error> {
let mut files = Vec::with_capacity(value.len());
for entry in value {
let entry: PackageEntry = entry.try_into()?;
files.push(entry);
}
Ok(Self { files })
}
}
#[test]
pub fn test_deb_packages_file() {
let input = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/deb822/packages/deb.debian.org_debian_dists_bullseye_main_binary-amd64_Packages"
));
let deserialized =
<Vec<PackagesFileRaw>>::deserialize(Deserializer::new(input.as_bytes())).unwrap();
//println!("{:?}", deserialized);
let parsed: PackagesFile = deserialized.try_into().unwrap();
//println!("{:?}", parsed);
assert_eq!(parsed.files.len(), 58618);
}

View File

@ -0,0 +1,618 @@
use std::collections::HashMap;
use anyhow::{bail, format_err, Error};
use rfc822_like::de::Deserializer;
use serde::Deserialize;
use serde_json::Value;
use super::CheckSums;
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct ReleaseFileRaw {
pub architectures: Option<String>,
pub changelogs: Option<String>,
pub codename: Option<String>,
pub components: Option<String>,
pub date: Option<String>,
pub description: Option<String>,
pub label: Option<String>,
pub origin: Option<String>,
pub suite: Option<String>,
pub version: Option<String>,
#[serde(rename = "MD5Sum")]
pub md5_sum: Option<String>,
#[serde(rename = "SHA1")]
pub sha1: Option<String>,
#[serde(rename = "SHA256")]
pub sha256: Option<String>,
#[serde(rename = "SHA512")]
pub sha512: Option<String>,
#[serde(flatten)]
pub extra_fields: HashMap<String, Value>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum CompressionType {
Bzip2,
Gzip,
Lzma,
Xz,
}
pub type Architecture = String;
pub type Component = String;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
/// Type of file reference extraced from path.
///
/// `Packages` and `Sources` will contain further reference to binary or source package files.
/// These are handled in `PackagesFile` and `SourcesFile` respectively.
pub enum FileReferenceType {
/// A `Commands` index listing command to package mappings
Commands(Architecture, Option<CompressionType>),
/// A `Contents` index listing contents of binary packages
Contents(Architecture, Option<CompressionType>),
/// A `Contents` index listing contents of binary udeb packages
ContentsUdeb(Architecture, Option<CompressionType>),
/// A DEP11 `Components` metadata file or `icons` archive
Dep11(Option<CompressionType>),
/// Referenced files which are not really part of the APT repository but only signed for trust-anchor reasons
Ignored,
/// PDiff indices
PDiff,
/// A `Packages` index listing binary package metadata and references
Packages(Architecture, Option<CompressionType>),
/// A compat `Release` file with no relevant content
PseudoRelease(Option<Architecture>),
/// A `Sources` index listing source package metadata and references
Sources(Option<CompressionType>),
/// A `Translation` file
Translation(Option<CompressionType>),
/// Unknown file reference
Unknown,
}
impl FileReferenceType {
fn match_compression(value: &str) -> Result<Option<CompressionType>, Error> {
if value.is_empty() {
return Ok(None);
}
let value = if let Some(stripped) = value.strip_prefix('.') {
stripped
} else {
value
};
match value {
"bz2" => Ok(Some(CompressionType::Bzip2)),
"gz" => Ok(Some(CompressionType::Gzip)),
"lzma" => Ok(Some(CompressionType::Lzma)),
"xz" => Ok(Some(CompressionType::Xz)),
other => bail!("Unexpected file extension '{other}'."),
}
}
pub fn parse(component: &str, path: &str) -> Result<FileReferenceType, Error> {
// everything referenced in a release file should be component-specific
let rest = path
.strip_prefix(&format!("{component}/"))
.ok_or_else(|| format_err!("Doesn't start with component '{component}'"))?;
let parse_binary_dir =
|file_name: &str, arch: &str| parse_binary_dir(file_name, arch, path);
if let Some((dir, rest)) = rest.split_once('/') {
// reference into another subdir
match dir {
"source" => {
// Sources or compat-Release
if let Some((dir, _rest)) = rest.split_once('/') {
if dir == "Sources.diff" {
Ok(FileReferenceType::PDiff)
} else {
Ok(FileReferenceType::Unknown)
}
} else if rest == "Release" {
Ok(FileReferenceType::PseudoRelease(None))
} else if let Some(ext) = rest.strip_prefix("Sources") {
let comp = FileReferenceType::match_compression(ext)?;
Ok(FileReferenceType::Sources(comp))
} else {
Ok(FileReferenceType::Unknown)
}
}
"cnf" => {
if let Some(rest) = rest.strip_prefix("Commands-") {
if let Some((arch, ext)) = rest.rsplit_once('.') {
Ok(FileReferenceType::Commands(
arch.to_owned(),
FileReferenceType::match_compression(ext).ok().flatten(),
))
} else {
Ok(FileReferenceType::Commands(rest.to_owned(), None))
}
} else {
Ok(FileReferenceType::Unknown)
}
}
"dep11" => {
if let Some((_path, ext)) = rest.rsplit_once('.') {
Ok(FileReferenceType::Dep11(
FileReferenceType::match_compression(ext).ok().flatten(),
))
} else {
Ok(FileReferenceType::Dep11(None))
}
}
"debian-installer" => {
// another layer, then like regular repo but pointing at udebs
if let Some((dir, rest)) = rest.split_once('/') {
if let Some(arch) = dir.strip_prefix("binary-") {
// Packages or compat-Release
return parse_binary_dir(rest, arch);
}
}
// all the rest
Ok(FileReferenceType::Unknown)
}
"i18n" => {
if let Some((dir, _rest)) = rest.split_once('/') {
if dir.starts_with("Translation") && dir.ends_with(".diff") {
Ok(FileReferenceType::PDiff)
} else {
Ok(FileReferenceType::Unknown)
}
} else if let Some((_, ext)) = rest.split_once('.') {
Ok(FileReferenceType::Translation(
FileReferenceType::match_compression(ext)?,
))
} else {
Ok(FileReferenceType::Translation(None))
}
}
_ => {
if let Some(arch) = dir.strip_prefix("binary-") {
// Packages or compat-Release
parse_binary_dir(rest, arch)
} else if let Some(_arch) = dir.strip_prefix("installer-") {
// netboot installer checksum files
Ok(FileReferenceType::Ignored)
} else {
// all the rest
Ok(FileReferenceType::Unknown)
}
}
}
} else if let Some(rest) = rest.strip_prefix("Contents-") {
// reference to a top-level file - Contents-*
let (rest, udeb) = if let Some(rest) = rest.strip_prefix("udeb-") {
(rest, true)
} else {
(rest, false)
};
let (arch, comp) = match rest.split_once('.') {
Some((arch, comp_str)) => (
arch.to_owned(),
FileReferenceType::match_compression(comp_str)?,
),
None => (rest.to_owned(), None),
};
if udeb {
Ok(FileReferenceType::ContentsUdeb(arch, comp))
} else {
Ok(FileReferenceType::Contents(arch, comp))
}
} else {
Ok(FileReferenceType::Unknown)
}
}
pub fn compression(&self) -> Option<CompressionType> {
match *self {
FileReferenceType::Commands(_, comp)
| FileReferenceType::Contents(_, comp)
| FileReferenceType::ContentsUdeb(_, comp)
| FileReferenceType::Packages(_, comp)
| FileReferenceType::Sources(comp)
| FileReferenceType::Translation(comp)
| FileReferenceType::Dep11(comp) => comp,
FileReferenceType::Unknown
| FileReferenceType::PDiff
| FileReferenceType::PseudoRelease(_)
| FileReferenceType::Ignored => None,
}
}
pub fn architecture(&self) -> Option<&Architecture> {
match self {
FileReferenceType::Commands(arch, _)
| FileReferenceType::Contents(arch, _)
| FileReferenceType::ContentsUdeb(arch, _)
| FileReferenceType::Packages(arch, _) => Some(arch),
FileReferenceType::PseudoRelease(arch) => arch.as_ref(),
FileReferenceType::Unknown
| FileReferenceType::PDiff
| FileReferenceType::Sources(_)
| FileReferenceType::Dep11(_)
| FileReferenceType::Translation(_)
| FileReferenceType::Ignored => None,
}
}
pub fn is_package_index(&self) -> bool {
matches!(self, FileReferenceType::Packages(_, _) | FileReferenceType::Sources(_))
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct FileReference {
pub path: String,
pub size: usize,
pub checksums: CheckSums,
pub component: Component,
pub file_type: FileReferenceType,
}
impl FileReference {
pub fn basename(&self) -> Result<String, Error> {
match self.file_type.compression() {
Some(_) => {
let (base, _ext) = self
.path
.rsplit_once('.')
.ok_or_else(|| format_err!("compressed file without file extension"))?;
Ok(base.to_owned())
}
None => Ok(self.path.clone()),
}
}
}
#[derive(Debug, Default, PartialEq, Eq)]
/// A parsed representation of a Release file
pub struct ReleaseFile {
/// List of architectures, e.g., `amd64` or `all`.
pub architectures: Vec<String>,
// TODO No-Support-for-Architecture-all
/// URL for changelog queries via `apt changelog`.
pub changelogs: Option<String>,
/// Release codename - single word, e.g., `bullseye`.
pub codename: Option<String>,
/// List of repository areas, e.g., `main`.
pub components: Vec<String>,
/// UTC timestamp of release file generation
pub date: Option<u64>,
/// UTC timestamp of release file expiration
pub valid_until: Option<u64>,
/// Repository description -
// TODO exact format?
pub description: Option<String>,
/// Repository label - single line
pub label: Option<String>,
/// Repository origin - single line
pub origin: Option<String>,
/// Release suite - single word, e.g., `stable`.
pub suite: Option<String>,
/// Release version
pub version: Option<String>,
/// Whether by-hash retrieval of referenced files is possible
pub aquire_by_hash: bool,
/// Files referenced by this `Release` file, e.g., packages indices.
///
/// Grouped by basename, since only the compressed version needs to actually exist on the repository server.
pub files: HashMap<String, Vec<FileReference>>,
}
impl TryFrom<ReleaseFileRaw> for ReleaseFile {
type Error = Error;
fn try_from(value: ReleaseFileRaw) -> Result<Self, Self::Error> {
let mut parsed = ReleaseFile {
architectures: whitespace_split_to_vec(
&value
.architectures
.ok_or_else(|| format_err!("'Architectures' field missing."))?,
),
components: whitespace_split_to_vec(
&value
.components
.ok_or_else(|| format_err!("'Components' field missing."))?,
),
changelogs: value.changelogs,
codename: value.codename,
date: value.date.as_deref().map(parse_date),
valid_until: value
.extra_fields
.get("Valid-Until")
.map(|val| parse_date(&val.to_string())),
description: value.description,
label: value.label,
origin: value.origin,
suite: value.suite,
files: HashMap::new(),
aquire_by_hash: false,
version: value.version,
};
if let Some(val) = value.extra_fields.get("Acquire-By-Hash") {
parsed.aquire_by_hash = *val == "yes";
}
// Fixup bullseye-security release files which have invalid components
if parsed.label.as_deref() == Some("Debian-Security")
&& parsed.codename.as_deref() == Some("bullseye-security")
{
parsed.components = parsed
.components
.into_iter()
.map(|comp| {
if let Some(stripped) = comp.strip_prefix("updates/") {
stripped.to_owned()
} else {
comp
}
})
.collect();
}
let mut references_map: HashMap<String, HashMap<String, FileReference>> = HashMap::new();
if let Some(md5) = value.md5_sum {
for line in md5.lines() {
let (mut file_ref, checksum) =
parse_file_reference(line, 16, parsed.components.as_ref())?;
let checksum = checksum
.try_into()
.map_err(|_err| format_err!("unexpected checksum length"))?;
file_ref.checksums.md5 = Some(checksum);
merge_references(&mut references_map, file_ref)?;
}
}
if let Some(sha1) = value.sha1 {
for line in sha1.lines() {
let (mut file_ref, checksum) =
parse_file_reference(line, 20, parsed.components.as_ref())?;
let checksum = checksum
.try_into()
.map_err(|_err| format_err!("unexpected checksum length"))?;
file_ref.checksums.sha1 = Some(checksum);
merge_references(&mut references_map, file_ref)?;
}
}
if let Some(sha256) = value.sha256 {
for line in sha256.lines() {
let (mut file_ref, checksum) =
parse_file_reference(line, 32, parsed.components.as_ref())?;
let checksum = checksum
.try_into()
.map_err(|_err| format_err!("unexpected checksum length"))?;
file_ref.checksums.sha256 = Some(checksum);
merge_references(&mut references_map, file_ref)?;
}
}
if let Some(sha512) = value.sha512 {
for line in sha512.lines() {
let (mut file_ref, checksum) =
parse_file_reference(line, 64, parsed.components.as_ref())?;
let checksum = checksum
.try_into()
.map_err(|_err| format_err!("unexpected checksum length"))?;
file_ref.checksums.sha512 = Some(checksum);
merge_references(&mut references_map, file_ref)?;
}
}
parsed.files =
references_map
.into_iter()
.fold(parsed.files, |mut map, (base, inner_map)| {
map.insert(base, inner_map.into_values().collect());
map
});
if let Some(insecure) = parsed
.files
.values()
.flatten()
.find(|file| !file.checksums.is_secure())
{
bail!(
"found file reference without strong checksum: {}",
insecure.path
);
}
Ok(parsed)
}
}
impl TryFrom<String> for ReleaseFile {
type Error = Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
value.as_bytes().try_into()
}
}
impl TryFrom<&[u8]> for ReleaseFile {
type Error = Error;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let deserialized = ReleaseFileRaw::deserialize(Deserializer::new(value))?;
deserialized.try_into()
}
}
fn whitespace_split_to_vec(list_str: &str) -> Vec<String> {
list_str
.split_ascii_whitespace()
.map(|arch| arch.to_owned())
.collect()
}
fn parse_file_reference(
line: &str,
csum_len: usize,
components: &[String],
) -> Result<(FileReference, Vec<u8>), Error> {
let mut split = line.split_ascii_whitespace();
let checksum = split
.next()
.ok_or_else(|| format_err!("No 'checksum' field in the file reference line."))?;
if checksum.len() > csum_len * 2 {
bail!(
"invalid checksum length: '{}', expected {} bytes",
checksum,
csum_len
);
}
let checksum = hex::decode(checksum)?;
let size = split
.next()
.ok_or_else(|| format_err!("No 'size' field in file reference line."))?
.parse::<usize>()?;
let file = split
.next()
.ok_or_else(|| format_err!("No 'path' field in file reference line."))?
.to_string();
let (component, file_type) = components
.iter()
.find_map(|component| {
if !file.starts_with(&format!("{component}/")) {
return None;
}
Some(
FileReferenceType::parse(component, &file)
.map(|file_type| (component.clone(), file_type)),
)
})
.unwrap_or_else(|| Ok(("UNKNOWN".to_string(), FileReferenceType::Unknown)))?;
Ok((
FileReference {
path: file,
size,
checksums: CheckSums::default(),
component,
file_type,
},
checksum,
))
}
fn parse_date(_date_str: &str) -> u64 {
// TODO implement
0
}
fn parse_binary_dir(file_name: &str, arch: &str, path: &str) -> Result<FileReferenceType, Error> {
if let Some((dir, _rest)) = file_name.split_once('/') {
if dir == "Packages.diff" {
// TODO re-evaluate?
Ok(FileReferenceType::PDiff)
} else {
Ok(FileReferenceType::Unknown)
}
} else if file_name == "Release" {
Ok(FileReferenceType::PseudoRelease(Some(arch.to_owned())))
} else {
let comp = match file_name.strip_prefix("Packages") {
None => {
bail!("found unexpected non-Packages reference to '{path}'")
}
Some(ext) => FileReferenceType::match_compression(ext)?,
};
//println!("compression: {comp:?}");
Ok(FileReferenceType::Packages(arch.to_owned(), comp))
}
}
fn merge_references(
base_map: &mut HashMap<String, HashMap<String, FileReference>>,
file_ref: FileReference,
) -> Result<(), Error> {
let base = file_ref.basename()?;
match base_map.get_mut(&base) {
None => {
let mut map = HashMap::new();
map.insert(file_ref.path.clone(), file_ref);
base_map.insert(base, map);
}
Some(entries) => {
match entries.get_mut(&file_ref.path) {
Some(entry) => {
if entry.size != file_ref.size {
bail!(
"Multiple entries for '{}' with size mismatch: {} / {}",
entry.path,
file_ref.size,
entry.size
);
}
entry.checksums.merge(&file_ref.checksums).map_err(|err| {
format_err!("Multiple checksums for '{}' - {err}", entry.path)
})?;
}
None => {
entries.insert(file_ref.path.clone(), file_ref);
}
};
}
};
Ok(())
}
#[test]
pub fn test_deb_release_file() {
let input = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/deb822/release/deb.debian.org_debian_dists_bullseye_Release"
));
let deserialized = ReleaseFileRaw::deserialize(Deserializer::new(input.as_bytes())).unwrap();
//println!("{:?}", deserialized);
let parsed: ReleaseFile = deserialized.try_into().unwrap();
//println!("{:?}", parsed);
assert_eq!(parsed.files.len(), 315);
}
#[test]
pub fn test_deb_release_file_insecure() {
let input = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/deb822/release/deb.debian.org_debian_dists_bullseye_Release_insecure"
));
let deserialized = ReleaseFileRaw::deserialize(Deserializer::new(input.as_bytes())).unwrap();
//println!("{:?}", deserialized);
let parsed: Result<ReleaseFile, Error> = deserialized.try_into();
assert!(parsed.is_err());
println!("{:?}", parsed);
}

View File

@ -0,0 +1,257 @@
use std::collections::HashMap;
use anyhow::{bail, Error, format_err};
use rfc822_like::de::Deserializer;
use serde::Deserialize;
use serde_json::Value;
use super::CheckSums;
//Uploaders
//
//Homepage
//
//Version Control System (VCS) fields
//
//Testsuite
//
//Dgit
//
//Standards-Version (mandatory)
//
//Build-Depends et al
//
//Package-List (recommended)
//
//Checksums-Sha1 and Checksums-Sha256 (mandatory)
//
//Files (mandatory)
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct SourcesFileRaw {
pub format: String,
pub package: String,
pub binary: Option<Vec<String>>,
pub version: String,
pub section: Option<String>,
pub priority: Option<String>,
pub maintainer: String,
pub uploaders: Option<String>,
pub architecture: Option<String>,
pub directory: String,
pub files: String,
#[serde(rename = "Checksums-Sha256")]
pub sha256: Option<String>,
#[serde(rename = "Checksums-Sha512")]
pub sha512: Option<String>,
#[serde(flatten)]
pub extra_fields: HashMap<String, Value>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct SourcePackageEntry {
pub format: String,
pub package: String,
pub binary: Option<Vec<String>>,
pub version: String,
pub architecture: Option<String>,
pub section: Option<String>,
pub priority: Option<String>,
pub maintainer: String,
pub uploaders: Option<String>,
pub directory: String,
pub files: HashMap<String, SourcePackageFileReference>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct SourcePackageFileReference {
pub file: String,
pub size: usize,
pub checksums: CheckSums,
}
impl SourcePackageEntry {
pub fn size(&self) -> usize {
self.files.values().map(|f| f.size).sum()
}
}
#[derive(Debug, Default, PartialEq, Eq)]
/// A parsed representation of a Release file
pub struct SourcesFile {
pub source_packages: Vec<SourcePackageEntry>,
}
impl TryFrom<SourcesFileRaw> for SourcePackageEntry {
type Error = Error;
fn try_from(value: SourcesFileRaw) -> Result<Self, Self::Error> {
let mut parsed = SourcePackageEntry {
package: value.package,
binary: value.binary,
version: value.version,
architecture: value.architecture,
files: HashMap::new(),
format: value.format,
section: value.section,
priority: value.priority,
maintainer: value.maintainer,
uploaders: value.uploaders,
directory: value.directory,
};
for file_reference in value.files.lines() {
let (file_name, size, md5) = parse_file_reference(file_reference, 16)?;
let entry = parsed.files.entry(file_name.clone()).or_insert_with(|| SourcePackageFileReference { file: file_name, size, checksums: CheckSums::default()});
entry.checksums.md5 = Some(md5.try_into().map_err(|_|format_err!("unexpected checksum length"))?);
if entry.size != size {
bail!("Size mismatch: {} != {}", entry.size, size);
}
}
if let Some(sha256) = value.sha256 {
for line in sha256.lines() {
let (file_name, size, sha256) = parse_file_reference(line, 32)?;
let entry = parsed.files.entry(file_name.clone()).or_insert_with(|| SourcePackageFileReference { file: file_name, size, checksums: CheckSums::default()});
entry.checksums.sha256 = Some(sha256.try_into().map_err(|_|format_err!("unexpected checksum length"))?);
if entry.size != size {
bail!("Size mismatch: {} != {}", entry.size, size);
}
}
};
if let Some(sha512) = value.sha512 {
for line in sha512.lines() {
let (file_name, size, sha512) = parse_file_reference(line, 64)?;
let entry = parsed.files.entry(file_name.clone()).or_insert_with(|| SourcePackageFileReference { file: file_name, size, checksums: CheckSums::default()});
entry.checksums.sha512 = Some(sha512.try_into().map_err(|_|format_err!("unexpected checksum length"))?);
if entry.size != size {
bail!("Size mismatch: {} != {}", entry.size, size);
}
}
};
for (file_name, reference) in &parsed.files {
if !reference.checksums.is_secure() {
bail!(
"no strong checksum found for source entry '{}'",
file_name
);
}
}
Ok(parsed)
}
}
impl TryFrom<String> for SourcesFile {
type Error = Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
value.as_bytes().try_into()
}
}
impl TryFrom<&[u8]> for SourcesFile {
type Error = Error;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let deserialized = <Vec<SourcesFileRaw>>::deserialize(Deserializer::new(value))?;
deserialized.try_into()
}
}
impl TryFrom<Vec<SourcesFileRaw>> for SourcesFile {
type Error = Error;
fn try_from(value: Vec<SourcesFileRaw>) -> Result<Self, Self::Error> {
let mut source_packages = Vec::with_capacity(value.len());
for entry in value {
let entry: SourcePackageEntry = entry.try_into()?;
source_packages.push(entry);
}
Ok(Self { source_packages })
}
}
fn parse_file_reference(
line: &str,
csum_len: usize,
) -> Result<(String, usize, Vec<u8>), Error> {
let mut split = line.split_ascii_whitespace();
let checksum = split
.next()
.ok_or_else(|| format_err!("Missing 'checksum' field."))?;
if checksum.len() > csum_len * 2 {
bail!(
"invalid checksum length: '{}', expected {} bytes",
checksum,
csum_len
);
}
let checksum = hex::decode(checksum)?;
let size = split
.next()
.ok_or_else(|| format_err!("Missing 'size' field."))?
.parse::<usize>()?;
let file = split
.next()
.ok_or_else(|| format_err!("Missing 'file name' field."))?
.to_string();
Ok((file, size, checksum))
}
#[test]
pub fn test_deb_packages_file() {
// NOTE: test is over an excerpt from packages starting with 0-9, a, b & z using:
// http://snapshot.debian.org/archive/debian/20221017T212657Z/dists/bullseye/main/source/Sources.xz
let input = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/deb822/sources/deb.debian.org_debian_dists_bullseye_main_source_Sources"
));
let deserialized =
<Vec<SourcesFileRaw>>::deserialize(Deserializer::new(input.as_bytes())).unwrap();
assert_eq!(deserialized.len(), 1558);
let parsed: SourcesFile = deserialized.try_into().unwrap();
assert_eq!(parsed.source_packages.len(), 1558);
let found = parsed.source_packages.iter().find(|source| source.package == "base-files").expect("test file contains 'base-files' entry");
assert_eq!(found.package, "base-files");
assert_eq!(found.format, "3.0 (native)");
assert_eq!(found.architecture.as_deref(), Some("any"));
assert_eq!(found.directory, "pool/main/b/base-files");
assert_eq!(found.section.as_deref(), Some("admin"));
assert_eq!(found.version, "11.1+deb11u5");
let binary_packages = found.binary.as_ref().expect("base-files source package builds base-files binary package");
assert_eq!(binary_packages.len(), 1);
assert_eq!(binary_packages[0], "base-files");
let references = &found.files;
assert_eq!(references.len(), 2);
let dsc_file = "base-files_11.1+deb11u5.dsc";
let dsc = references.get(dsc_file).expect("base-files source package contains 'dsc' reference");
assert_eq!(dsc.file, dsc_file);
assert_eq!(dsc.size, 1110);
assert_eq!(dsc.checksums.md5.expect("dsc has md5 checksum"), hex::decode("741c34ac0151262a03de8d5a07bc4271").unwrap()[..]);
assert_eq!(dsc.checksums.sha256.expect("dsc has sha256 checksum"), hex::decode("c41a7f00d57759f27e6068240d1ea7ad80a9a752e4fb43850f7e86e967422bd3").unwrap()[..]);
let tar_file = "base-files_11.1+deb11u5.tar.xz";
let tar = references.get(tar_file).expect("base-files source package contains 'tar' reference");
assert_eq!(tar.file, tar_file);
assert_eq!(tar.size, 65612);
assert_eq!(tar.checksums.md5.expect("tar has md5 checksum"), hex::decode("995df33642118b566a4026410e1c6aac").unwrap()[..]);
assert_eq!(tar.checksums.sha256.expect("tar has sha256 checksum"), hex::decode("31c9e5745845a73f3d5c8a7868c379d77aaca42b81194679d7ab40cc28e3a0e9").unwrap()[..]);
}

3
proxmox-apt/src/lib.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod config;
pub mod deb822;
pub mod repositories;

View File

@ -0,0 +1,469 @@
use std::fmt::Display;
use std::path::{Path, PathBuf};
use anyhow::{format_err, Error};
use serde::{Deserialize, Serialize};
use crate::repositories::release::DebianCodename;
use crate::repositories::repository::{
APTRepository, APTRepositoryFileType, APTRepositoryPackageType,
};
use proxmox_schema::api;
mod list_parser;
use list_parser::APTListFileParser;
mod sources_parser;
use sources_parser::APTSourcesFileParser;
trait APTRepositoryParser {
/// Parse all repositories including the disabled ones and push them onto
/// the provided vector.
fn parse_repositories(&mut self) -> Result<Vec<APTRepository>, Error>;
}
#[api(
properties: {
"file-type": {
type: APTRepositoryFileType,
},
repositories: {
description: "List of APT repositories.",
type: Array,
items: {
type: APTRepository,
},
},
digest: {
description: "Digest for the content of the file.",
optional: true,
type: Array,
items: {
description: "Digest byte.",
type: Integer,
},
},
},
)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
/// Represents an abstract APT repository file.
pub struct APTRepositoryFile {
/// The path to the file. If None, `contents` must be set directly.
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
/// The type of the file.
pub file_type: APTRepositoryFileType,
/// List of repositories in the file.
pub repositories: Vec<APTRepository>,
/// The file content, if already parsed.
pub content: Option<String>,
/// Digest of the original contents.
pub digest: Option<[u8; 32]>,
}
#[api]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
/// Error type for problems with APT repository files.
pub struct APTRepositoryFileError {
/// The path to the problematic file.
pub path: String,
/// The error message.
pub error: String,
}
impl Display for APTRepositoryFileError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "proxmox-apt error for '{}' - {}", self.path, self.error)
}
}
impl std::error::Error for APTRepositoryFileError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
#[api]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
/// Additional information for a repository.
pub struct APTRepositoryInfo {
/// Path to the defining file.
#[serde(skip_serializing_if = "String::is_empty")]
pub path: String,
/// Index of the associated respository within the file (starting from 0).
pub index: usize,
/// The property from which the info originates (e.g. "Suites")
#[serde(skip_serializing_if = "Option::is_none")]
pub property: Option<String>,
/// Info kind (e.g. "warning")
pub kind: String,
/// Info message
pub message: String,
}
impl APTRepositoryFile {
/// Creates a new `APTRepositoryFile` without parsing.
///
/// If the file is hidden, the path points to a directory, or the extension
/// is usually ignored by APT (e.g. `.orig`), `Ok(None)` is returned, while
/// invalid file names yield an error.
pub fn new<P: AsRef<Path>>(path: P) -> Result<Option<Self>, APTRepositoryFileError> {
let path: PathBuf = path.as_ref().to_path_buf();
let new_err = |path_string: String, err: &str| APTRepositoryFileError {
path: path_string,
error: err.to_string(),
};
let path_string = path
.clone()
.into_os_string()
.into_string()
.map_err(|os_string| {
new_err(
os_string.to_string_lossy().to_string(),
"path is not valid unicode",
)
})?;
let new_err = |err| new_err(path_string.clone(), err);
if path.is_dir() {
return Ok(None);
}
let file_name = match path.file_name() {
Some(file_name) => file_name
.to_os_string()
.into_string()
.map_err(|_| new_err("invalid path"))?,
None => return Err(new_err("invalid path")),
};
if file_name.starts_with('.') || file_name.ends_with('~') {
return Ok(None);
}
let extension = match path.extension() {
Some(extension) => extension
.to_os_string()
.into_string()
.map_err(|_| new_err("invalid path"))?,
None => return Err(new_err("invalid extension")),
};
// See APT's apt-pkg/init.cc
if extension.starts_with("dpkg-")
|| extension.starts_with("ucf-")
|| matches!(
extension.as_str(),
"disabled" | "bak" | "save" | "orig" | "distUpgrade"
)
{
return Ok(None);
}
let file_type = APTRepositoryFileType::try_from(&extension[..])
.map_err(|_| new_err("invalid extension"))?;
if !file_name
.chars()
.all(|x| x.is_ascii_alphanumeric() || x == '_' || x == '-' || x == '.')
{
return Err(new_err("invalid characters in file name"));
}
Ok(Some(Self {
path: Some(path_string),
file_type,
repositories: vec![],
digest: None,
content: None,
}))
}
pub fn with_content(content: String, content_type: APTRepositoryFileType) -> Self {
Self {
file_type: content_type,
content: Some(content),
path: None,
repositories: vec![],
digest: None,
}
}
/// Check if the file exists.
pub fn exists(&self) -> bool {
if let Some(path) = &self.path {
PathBuf::from(path).exists()
} else {
false
}
}
pub fn read_with_digest(&self) -> Result<(Vec<u8>, [u8; 32]), APTRepositoryFileError> {
if let Some(path) = &self.path {
let content = std::fs::read(path).map_err(|err| self.err(format_err!("{}", err)))?;
let digest = openssl::sha::sha256(&content);
Ok((content, digest))
} else if let Some(ref content) = self.content {
let content = content.as_bytes();
let digest = openssl::sha::sha256(content);
Ok((content.to_vec(), digest))
} else {
Err(self.err(format_err!(
"Neither 'path' nor 'content' set, cannot read APT repository info."
)))
}
}
/// Create an `APTRepositoryFileError`.
pub fn err(&self, error: Error) -> APTRepositoryFileError {
APTRepositoryFileError {
path: self.path.clone().unwrap_or_default(),
error: error.to_string(),
}
}
/// Parses the APT repositories configured in the file on disk, including
/// disabled ones.
///
/// Resets the current repositories and digest, even on failure.
pub fn parse(&mut self) -> Result<(), APTRepositoryFileError> {
self.repositories.clear();
self.digest = None;
let (content, digest) = self.read_with_digest()?;
let mut parser: Box<dyn APTRepositoryParser> = match self.file_type {
APTRepositoryFileType::List => Box::new(APTListFileParser::new(&content[..])),
APTRepositoryFileType::Sources => Box::new(APTSourcesFileParser::new(&content[..])),
};
let repos = parser.parse_repositories().map_err(|err| self.err(err))?;
for (n, repo) in repos.iter().enumerate() {
repo.basic_check()
.map_err(|err| self.err(format_err!("check for repository {} - {}", n + 1, err)))?;
}
self.repositories = repos;
self.digest = Some(digest);
Ok(())
}
/// Writes the repositories to the file on disk.
///
/// If a digest is provided, checks that the current content of the file still
/// produces the same one.
pub fn write(&self) -> Result<(), APTRepositoryFileError> {
let path = match &self.path {
Some(path) => path,
None => {
return Err(self.err(format_err!(
"Cannot write to APT repository file without path."
)));
}
};
if let Some(digest) = self.digest {
if !self.exists() {
return Err(self.err(format_err!("digest specified, but file does not exist")));
}
let (_, current_digest) = self.read_with_digest()?;
if digest != current_digest {
return Err(self.err(format_err!("digest mismatch")));
}
}
if self.repositories.is_empty() {
return std::fs::remove_file(&path)
.map_err(|err| self.err(format_err!("unable to remove file - {}", err)));
}
let mut content = vec![];
for (n, repo) in self.repositories.iter().enumerate() {
repo.basic_check()
.map_err(|err| self.err(format_err!("check for repository {} - {}", n + 1, err)))?;
repo.write(&mut content)
.map_err(|err| self.err(format_err!("writing repository {} - {}", n + 1, err)))?;
}
let path = PathBuf::from(&path);
let dir = match path.parent() {
Some(dir) => dir,
None => return Err(self.err(format_err!("invalid path"))),
};
std::fs::create_dir_all(dir)
.map_err(|err| self.err(format_err!("unable to create parent dir - {}", err)))?;
let pid = std::process::id();
let mut tmp_path = path.clone();
tmp_path.set_extension("tmp");
tmp_path.set_extension(format!("{}", pid));
if let Err(err) = std::fs::write(&tmp_path, content) {
let _ = std::fs::remove_file(&tmp_path);
return Err(self.err(format_err!("writing {:?} failed - {}", path, err)));
}
if let Err(err) = std::fs::rename(&tmp_path, &path) {
let _ = std::fs::remove_file(&tmp_path);
return Err(self.err(format_err!("rename failed for {:?} - {}", path, err)));
}
Ok(())
}
/// Checks if old or unstable suites are configured and that the Debian security repository
/// has the correct suite. Also checks that the `stable` keyword is not used.
pub fn check_suites(&self, current_codename: DebianCodename) -> Vec<APTRepositoryInfo> {
let mut infos = vec![];
let path = match &self.path {
Some(path) => path.clone(),
None => return vec![],
};
for (n, repo) in self.repositories.iter().enumerate() {
if !repo.types.contains(&APTRepositoryPackageType::Deb) {
continue;
}
let is_security_repo = repo.uris.iter().any(|uri| {
let uri = uri.trim_end_matches('/');
let uri = uri.strip_suffix("debian-security").unwrap_or(uri);
let uri = uri.trim_end_matches('/');
matches!(
uri,
"http://security.debian.org" | "https://security.debian.org",
)
});
let require_suffix = match is_security_repo {
true if current_codename >= DebianCodename::Bullseye => Some("-security"),
true => Some("/updates"),
false => None,
};
let mut add_info = |kind: &str, message| {
infos.push(APTRepositoryInfo {
path: path.clone(),
index: n,
property: Some("Suites".to_string()),
kind: kind.to_string(),
message,
})
};
let message_old = |suite| format!("old suite '{}' configured!", suite);
let message_new =
|suite| format!("suite '{}' should not be used in production!", suite);
let message_stable = "use the name of the stable distribution instead of 'stable'!";
for suite in repo.suites.iter() {
let (base_suite, suffix) = suite_variant(suite);
match base_suite {
"oldoldstable" | "oldstable" => {
add_info("warning", message_old(base_suite));
}
"testing" | "unstable" | "experimental" | "sid" => {
add_info("warning", message_new(base_suite));
}
"stable" => {
add_info("warning", message_stable.to_string());
}
_ => (),
};
let codename: DebianCodename = match base_suite.try_into() {
Ok(codename) => codename,
Err(_) => continue,
};
if codename < current_codename {
add_info("warning", message_old(base_suite));
}
if Some(codename) == current_codename.next() {
add_info("ignore-pre-upgrade-warning", message_new(base_suite));
} else if codename > current_codename {
add_info("warning", message_new(base_suite));
}
if let Some(require_suffix) = require_suffix {
if suffix != require_suffix {
add_info(
"warning",
format!("expected suite '{}{}'", current_codename, require_suffix),
);
}
}
}
}
infos
}
/// Checks for official URIs.
pub fn check_uris(&self) -> Vec<APTRepositoryInfo> {
let mut infos = vec![];
let path = match &self.path {
Some(path) => path,
None => return vec![],
};
for (n, repo) in self.repositories.iter().enumerate() {
let mut origin = match repo.get_cached_origin() {
Ok(option) => option,
Err(_) => None,
};
if origin.is_none() {
origin = repo.origin_from_uris();
}
if let Some(origin) = origin {
infos.push(APTRepositoryInfo {
path: path.clone(),
index: n,
kind: "origin".to_string(),
property: None,
message: origin,
});
}
}
infos
}
}
/// Splits the suite into its base part and variant.
/// Does not expect the base part to contain either `-` or `/`.
fn suite_variant(suite: &str) -> (&str, &str) {
match suite.find(&['-', '/'][..]) {
Some(n) => (&suite[0..n], &suite[n..]),
None => (suite, ""),
}
}

View File

@ -0,0 +1,250 @@
use std::io::BufRead;
use std::iter::Iterator;
use anyhow::{bail, format_err, Error};
use crate::repositories::{APTRepository, APTRepositoryFileType, APTRepositoryOption};
use super::APTRepositoryParser;
// TODO convert %-escape characters. Also adapt printing back accordingly,
// because at least '%' needs to be re-escaped when printing.
/// See APT's ParseQuoteWord in contrib/strutl.cc
///
/// Doesn't split on whitespace when between `[]` or `""` and strips `"` from the word.
///
/// Currently, %-escaped characters are not interpreted, but passed along as is.
struct SplitQuoteWord {
rest: String,
position: usize,
}
impl SplitQuoteWord {
pub fn new(string: String) -> Self {
Self {
rest: string,
position: 0,
}
}
}
impl Iterator for SplitQuoteWord {
type Item = Result<String, Error>;
fn next(&mut self) -> Option<Self::Item> {
let rest = &self.rest[self.position..];
let mut start = None;
let mut wait_for = None;
for (n, c) in rest.chars().enumerate() {
self.position += 1;
if let Some(wait_for_char) = wait_for {
if wait_for_char == c {
wait_for = None;
}
continue;
}
if char::is_ascii_whitespace(&c) {
if let Some(start) = start {
return Some(Ok(rest[start..n].replace('"', "")));
}
continue;
}
if start == None {
start = Some(n);
}
if c == '"' {
wait_for = Some('"');
}
if c == '[' {
wait_for = Some(']');
}
}
if let Some(wait_for) = wait_for {
return Some(Err(format_err!("missing terminating '{}'", wait_for)));
}
if let Some(start) = start {
return Some(Ok(rest[start..].replace('"', "")));
}
None
}
}
pub struct APTListFileParser<R: BufRead> {
input: R,
line_nr: usize,
comment: String,
}
impl<R: BufRead> APTListFileParser<R> {
pub fn new(reader: R) -> Self {
Self {
input: reader,
line_nr: 0,
comment: String::new(),
}
}
/// Helper to parse options from the existing token stream.
///
/// Also returns `Ok(())` if there are no options.
///
/// Errors when options are invalid or not closed by `']'`.
fn parse_options(
options: &mut Vec<APTRepositoryOption>,
tokens: &mut SplitQuoteWord,
) -> Result<(), Error> {
let mut finished = false;
loop {
let mut option = match tokens.next() {
Some(token) => token?,
None => bail!("options not closed by ']'"),
};
if let Some(stripped) = option.strip_suffix(']') {
option = stripped.to_string();
if option.is_empty() {
break;
}
finished = true; // but still need to handle the last one
};
if let Some(mid) = option.find('=') {
let (key, mut value_str) = option.split_at(mid);
value_str = &value_str[1..];
if key.is_empty() {
bail!("option has no key: '{}'", option);
}
if value_str.is_empty() {
bail!("option has no value: '{}'", option);
}
let values: Vec<String> = value_str
.split(',')
.map(|value| value.to_string())
.collect();
options.push(APTRepositoryOption {
key: key.to_string(),
values,
});
} else if !option.is_empty() {
bail!("got invalid option - '{}'", option);
}
if finished {
break;
}
}
Ok(())
}
/// Parse a repository or comment in one-line format.
///
/// Commented out repositories are also detected and returned with the
/// `enabled` property set to `false`.
///
/// If the line contains a repository, `self.comment` is added to the
/// `comment` property.
///
/// If the line contains a comment, it is added to `self.comment`.
fn parse_one_line(&mut self, mut line: &str) -> Result<Option<APTRepository>, Error> {
line = line.trim_matches(|c| char::is_ascii_whitespace(&c));
// check for commented out repository first
if let Some(commented_out) = line.strip_prefix('#') {
if let Ok(Some(mut repo)) = self.parse_one_line(commented_out) {
repo.set_enabled(false);
return Ok(Some(repo));
}
}
let mut repo = APTRepository::new(APTRepositoryFileType::List);
// now handle "real" comment
if let Some(comment_start) = line.find('#') {
let (line_start, comment) = line.split_at(comment_start);
self.comment = format!("{}{}\n", self.comment, &comment[1..]);
line = line_start;
}
// e.g. quoted "deb" is not accepted by APT, so no need for quote word parsing here
line = match line.split_once(|c| char::is_ascii_whitespace(&c)) {
Some((package_type, rest)) => {
repo.types.push(package_type.try_into()?);
rest
}
None => return Ok(None), // empty line
};
line = line.trim_start_matches(|c| char::is_ascii_whitespace(&c));
let has_options = match line.strip_prefix('[') {
Some(rest) => {
// avoid the start of the options to be interpreted as the start of a quote word
line = rest;
true
}
None => false,
};
let mut tokens = SplitQuoteWord::new(line.to_string());
if has_options {
Self::parse_options(&mut repo.options, &mut tokens)?;
}
// the rest of the line is just '<uri> <suite> [<components>...]'
repo.uris
.push(tokens.next().ok_or_else(|| format_err!("missing URI"))??);
repo.suites.push(
tokens
.next()
.ok_or_else(|| format_err!("missing suite"))??,
);
for token in tokens {
repo.components.push(token?);
}
repo.comment = std::mem::take(&mut self.comment);
Ok(Some(repo))
}
}
impl<R: BufRead> APTRepositoryParser for APTListFileParser<R> {
fn parse_repositories(&mut self) -> Result<Vec<APTRepository>, Error> {
let mut repos = vec![];
let mut line = String::new();
loop {
self.line_nr += 1;
line.clear();
match self.input.read_line(&mut line) {
Err(err) => bail!("input error - {}", err),
Ok(0) => break,
Ok(_) => match self.parse_one_line(&line) {
Ok(Some(repo)) => repos.push(repo),
Ok(None) => continue,
Err(err) => bail!("malformed entry on line {} - {}", self.line_nr, err),
},
}
}
Ok(repos)
}
}

View File

@ -0,0 +1,203 @@
use std::io::BufRead;
use std::iter::Iterator;
use anyhow::{bail, Error};
use crate::repositories::{
APTRepository, APTRepositoryFileType, APTRepositoryOption, APTRepositoryPackageType,
};
use super::APTRepositoryParser;
pub struct APTSourcesFileParser<R: BufRead> {
input: R,
stanza_nr: usize,
comment: String,
}
/// See `man sources.list` and `man deb822` for the format specification.
impl<R: BufRead> APTSourcesFileParser<R> {
pub fn new(reader: R) -> Self {
Self {
input: reader,
stanza_nr: 1,
comment: String::new(),
}
}
/// Based on APT's `StringToBool` in `strutl.cc`
fn string_to_bool(string: &str, default: bool) -> bool {
let string = string.trim_matches(|c| char::is_ascii_whitespace(&c));
let string = string.to_lowercase();
match &string[..] {
"1" | "yes" | "true" | "with" | "on" | "enable" => true,
"0" | "no" | "false" | "without" | "off" | "disable" => false,
_ => default,
}
}
/// Checks if `key` is valid according to deb822
fn valid_key(key: &str) -> bool {
if key.starts_with('-') {
return false;
};
return key.chars().all(|c| matches!(c, '!'..='9' | ';'..='~'));
}
/// Try parsing a repository in stanza format from `lines`.
///
/// Returns `Ok(None)` when no stanza can be found.
///
/// Comments are added to `self.comments`. If a stanza can be found,
/// `self.comment` is added to the repository's `comment` property.
///
/// Fully commented out stanzas are treated as comments.
fn parse_stanza(&mut self, lines: &str) -> Result<Option<APTRepository>, Error> {
let mut repo = APTRepository::new(APTRepositoryFileType::Sources);
// Values may be folded into multiple lines.
// Those lines have to start with a space or a tab.
let lines = lines.replace("\n ", " ");
let lines = lines.replace("\n\t", " ");
let mut got_something = false;
for line in lines.lines() {
let line = line.trim_matches(|c| char::is_ascii_whitespace(&c));
if line.is_empty() {
continue;
}
if let Some(commented_out) = line.strip_prefix('#') {
self.comment = format!("{}{}\n", self.comment, commented_out);
continue;
}
if let Some(mid) = line.find(':') {
let (key, value_str) = line.split_at(mid);
let value_str = &value_str[1..];
let key = key.trim_matches(|c| char::is_ascii_whitespace(&c));
if key.is_empty() {
bail!("option has no key: '{}'", line);
}
if value_str.is_empty() {
// ignored by APT
eprintln!("option has no value: '{}'", line);
continue;
}
if !Self::valid_key(key) {
// ignored by APT
eprintln!("option with invalid key '{}'", key);
continue;
}
let values: Vec<String> = value_str
.split_ascii_whitespace()
.map(|value| value.to_string())
.collect();
match &key.to_lowercase()[..] {
"types" => {
if !repo.types.is_empty() {
eprintln!("key 'Types' was defined twice");
}
let mut types = Vec::<APTRepositoryPackageType>::new();
for package_type in values {
types.push((&package_type[..]).try_into()?);
}
repo.types = types;
}
"uris" => {
if !repo.uris.is_empty() {
eprintln!("key 'URIs' was defined twice");
}
repo.uris = values;
}
"suites" => {
if !repo.suites.is_empty() {
eprintln!("key 'Suites' was defined twice");
}
repo.suites = values;
}
"components" => {
if !repo.components.is_empty() {
eprintln!("key 'Components' was defined twice");
}
repo.components = values;
}
"enabled" => {
repo.set_enabled(Self::string_to_bool(value_str, true));
}
_ => repo.options.push(APTRepositoryOption {
key: key.to_string(),
values,
}),
}
} else {
bail!("got invalid line - '{:?}'", line);
}
got_something = true;
}
if !got_something {
return Ok(None);
}
repo.comment = std::mem::take(&mut self.comment);
Ok(Some(repo))
}
/// Helper function for `parse_repositories`.
fn try_parse_stanza(
&mut self,
lines: &str,
repos: &mut Vec<APTRepository>,
) -> Result<(), Error> {
match self.parse_stanza(lines) {
Ok(Some(repo)) => {
repos.push(repo);
self.stanza_nr += 1;
}
Ok(None) => (),
Err(err) => bail!("malformed entry in stanza {} - {}", self.stanza_nr, err),
}
Ok(())
}
}
impl<R: BufRead> APTRepositoryParser for APTSourcesFileParser<R> {
fn parse_repositories(&mut self) -> Result<Vec<APTRepository>, Error> {
let mut repos = vec![];
let mut lines = String::new();
loop {
let old_length = lines.len();
match self.input.read_line(&mut lines) {
Err(err) => bail!("input error - {}", err),
Ok(0) => {
self.try_parse_stanza(&lines[..], &mut repos)?;
break;
}
Ok(_) => {
if (lines[old_length..])
.trim_matches(|c| char::is_ascii_whitespace(&c))
.is_empty()
{
// detected end of stanza
self.try_parse_stanza(&lines[..], &mut repos)?;
lines.clear();
}
}
}
}
Ok(repos)
}
}

View File

@ -0,0 +1,191 @@
use std::collections::BTreeMap;
use std::path::PathBuf;
use anyhow::{bail, Error};
mod repository;
pub use repository::{
APTRepository, APTRepositoryFileType, APTRepositoryOption, APTRepositoryPackageType,
};
mod file;
pub use file::{APTRepositoryFile, APTRepositoryFileError, APTRepositoryInfo};
mod release;
pub use release::{get_current_release_codename, DebianCodename};
mod standard;
pub use standard::{APTRepositoryHandle, APTStandardRepository};
const APT_SOURCES_LIST_FILENAME: &str = "/etc/apt/sources.list";
const APT_SOURCES_LIST_DIRECTORY: &str = "/etc/apt/sources.list.d/";
/// Calculates a common digest for successfully parsed repository files.
///
/// The digest is invariant with respect to file order.
///
/// Files without a digest are ignored.
fn common_digest(files: &[APTRepositoryFile]) -> [u8; 32] {
let mut digests = BTreeMap::new();
for file in files.iter() {
digests.insert(file.path.clone(), &file.digest);
}
let mut common_raw = Vec::<u8>::with_capacity(digests.len() * 32);
for digest in digests.values() {
match digest {
Some(digest) => common_raw.extend_from_slice(&digest[..]),
None => (),
}
}
openssl::sha::sha256(&common_raw[..])
}
/// Provides additional information about the repositories.
///
/// The kind of information can be:
/// `warnings` for bad suites.
/// `ignore-pre-upgrade-warning` when the next stable suite is configured.
/// `badge` for official URIs.
pub fn check_repositories(
files: &[APTRepositoryFile],
current_suite: DebianCodename,
) -> Vec<APTRepositoryInfo> {
let mut infos = vec![];
for file in files.iter() {
infos.append(&mut file.check_suites(current_suite));
infos.append(&mut file.check_uris());
}
infos
}
/// Get the repository associated to the handle and the path where it is usually configured.
pub fn get_standard_repository(
handle: APTRepositoryHandle,
product: &str,
suite: DebianCodename,
) -> (APTRepository, String) {
let repo = handle.to_repository(product, &suite.to_string());
let path = handle.path(product);
(repo, path)
}
/// Return handles for standard Proxmox repositories and their status, where
/// `None` means not configured, and `Some(bool)` indicates enabled or disabled.
pub fn standard_repositories(
files: &[APTRepositoryFile],
product: &str,
suite: DebianCodename,
) -> Vec<APTStandardRepository> {
let mut result = vec![
APTStandardRepository::from(APTRepositoryHandle::Enterprise),
APTStandardRepository::from(APTRepositoryHandle::NoSubscription),
APTStandardRepository::from(APTRepositoryHandle::Test),
];
if product == "pve" {
result.append(&mut vec![
APTStandardRepository::from(APTRepositoryHandle::CephQuincy),
APTStandardRepository::from(APTRepositoryHandle::CephQuincyTest),
APTStandardRepository::from(APTRepositoryHandle::CephPacific),
APTStandardRepository::from(APTRepositoryHandle::CephPacificTest),
APTStandardRepository::from(APTRepositoryHandle::CephOctopus),
APTStandardRepository::from(APTRepositoryHandle::CephOctopusTest),
]);
}
for file in files.iter() {
for repo in file.repositories.iter() {
for entry in result.iter_mut() {
if entry.status == Some(true) {
continue;
}
if repo.is_referenced_repository(entry.handle, product, &suite.to_string())
|| repo.is_referenced_repository(
entry.handle,
product,
&suite.next().unwrap().to_string(),
) {
entry.status = Some(repo.enabled);
}
}
}
}
result
}
/// Returns all APT repositories configured in `/etc/apt/sources.list` and
/// in `/etc/apt/sources.list.d` including disabled repositories.
///
/// Returns the succesfully parsed files, a list of errors for files that could
/// not be read or parsed and a common digest for the succesfully parsed files.
///
/// The digest is guaranteed to be set for each successfully parsed file.
pub fn repositories() -> Result<
(
Vec<APTRepositoryFile>,
Vec<APTRepositoryFileError>,
[u8; 32],
),
Error,
> {
let to_result = |files: Vec<APTRepositoryFile>, errors: Vec<APTRepositoryFileError>| {
let common_digest = common_digest(&files);
(files, errors, common_digest)
};
let mut files = vec![];
let mut errors = vec![];
let sources_list_path = PathBuf::from(APT_SOURCES_LIST_FILENAME);
let sources_list_d_path = PathBuf::from(APT_SOURCES_LIST_DIRECTORY);
match APTRepositoryFile::new(sources_list_path) {
Ok(Some(mut file)) => match file.parse() {
Ok(()) => files.push(file),
Err(err) => errors.push(err),
},
_ => bail!("internal error with '{}'", APT_SOURCES_LIST_FILENAME),
}
if !sources_list_d_path.exists() {
return Ok(to_result(files, errors));
}
if !sources_list_d_path.is_dir() {
errors.push(APTRepositoryFileError {
path: APT_SOURCES_LIST_DIRECTORY.to_string(),
error: "not a directory!".to_string(),
});
return Ok(to_result(files, errors));
}
for entry in std::fs::read_dir(sources_list_d_path)? {
let path = entry?.path();
match APTRepositoryFile::new(path) {
Ok(Some(mut file)) => match file.parse() {
Ok(()) => {
if file.digest.is_none() {
bail!("internal error - digest not set");
}
files.push(file);
}
Err(err) => errors.push(err),
},
Ok(None) => (),
Err(err) => errors.push(err),
}
}
Ok(to_result(files, errors))
}

View File

@ -0,0 +1,100 @@
use std::fmt::Display;
use std::io::{BufRead, BufReader};
use anyhow::{bail, format_err, Error};
/// The code names of Debian releases. Does not include `sid`.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum DebianCodename {
Lenny = 5,
Squeeze,
Wheezy,
Jessie,
Stretch,
Buster,
Bullseye,
Bookworm,
Trixie,
}
impl DebianCodename {
pub fn next(&self) -> Option<Self> {
match (*self as u8 + 1).try_into() {
Ok(codename) => Some(codename),
Err(_) => None,
}
}
}
impl TryFrom<&str> for DebianCodename {
type Error = Error;
fn try_from(string: &str) -> Result<Self, Error> {
match string {
"lenny" => Ok(DebianCodename::Lenny),
"squeeze" => Ok(DebianCodename::Squeeze),
"wheezy" => Ok(DebianCodename::Wheezy),
"jessie" => Ok(DebianCodename::Jessie),
"stretch" => Ok(DebianCodename::Stretch),
"buster" => Ok(DebianCodename::Buster),
"bullseye" => Ok(DebianCodename::Bullseye),
"bookworm" => Ok(DebianCodename::Bookworm),
"trixie" => Ok(DebianCodename::Trixie),
_ => bail!("unknown Debian code name '{}'", string),
}
}
}
impl TryFrom<u8> for DebianCodename {
type Error = Error;
fn try_from(number: u8) -> Result<Self, Error> {
match number {
5 => Ok(DebianCodename::Lenny),
6 => Ok(DebianCodename::Squeeze),
7 => Ok(DebianCodename::Wheezy),
8 => Ok(DebianCodename::Jessie),
9 => Ok(DebianCodename::Stretch),
10 => Ok(DebianCodename::Buster),
11 => Ok(DebianCodename::Bullseye),
12 => Ok(DebianCodename::Bookworm),
13 => Ok(DebianCodename::Trixie),
_ => bail!("unknown Debian release number '{}'", number),
}
}
}
impl Display for DebianCodename {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DebianCodename::Lenny => write!(f, "lenny"),
DebianCodename::Squeeze => write!(f, "squeeze"),
DebianCodename::Wheezy => write!(f, "wheezy"),
DebianCodename::Jessie => write!(f, "jessie"),
DebianCodename::Stretch => write!(f, "stretch"),
DebianCodename::Buster => write!(f, "buster"),
DebianCodename::Bullseye => write!(f, "bullseye"),
DebianCodename::Bookworm => write!(f, "bookworm"),
DebianCodename::Trixie => write!(f, "trixie"),
}
}
}
/// Read the `VERSION_CODENAME` from `/etc/os-release`.
pub fn get_current_release_codename() -> Result<DebianCodename, Error> {
let raw = std::fs::read("/etc/os-release")
.map_err(|err| format_err!("unable to read '/etc/os-release' - {}", err))?;
let reader = BufReader::new(&*raw);
for line in reader.lines() {
let line = line.map_err(|err| format_err!("unable to read '/etc/os-release' - {}", err))?;
if let Some(codename) = line.strip_prefix("VERSION_CODENAME=") {
let codename = codename.trim_matches(&['"', '\''][..]);
return codename.try_into();
}
}
bail!("unable to parse codename from '/etc/os-release'");
}

View File

@ -0,0 +1,555 @@
use std::fmt::Display;
use std::io::{BufRead, BufReader, Write};
use std::path::PathBuf;
use anyhow::{bail, format_err, Error};
use serde::{Deserialize, Serialize};
use proxmox_schema::api;
use crate::repositories::standard::APTRepositoryHandle;
#[api]
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum APTRepositoryFileType {
/// One-line-style format
List,
/// DEB822-style format
Sources,
}
impl TryFrom<&str> for APTRepositoryFileType {
type Error = Error;
fn try_from(string: &str) -> Result<Self, Error> {
match string {
"list" => Ok(APTRepositoryFileType::List),
"sources" => Ok(APTRepositoryFileType::Sources),
_ => bail!("invalid file type '{}'", string),
}
}
}
impl Display for APTRepositoryFileType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
APTRepositoryFileType::List => write!(f, "list"),
APTRepositoryFileType::Sources => write!(f, "sources"),
}
}
}
#[api]
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum APTRepositoryPackageType {
/// Debian package
Deb,
/// Debian source package
DebSrc,
}
impl TryFrom<&str> for APTRepositoryPackageType {
type Error = Error;
fn try_from(string: &str) -> Result<Self, Error> {
match string {
"deb" => Ok(APTRepositoryPackageType::Deb),
"deb-src" => Ok(APTRepositoryPackageType::DebSrc),
_ => bail!("invalid package type '{}'", string),
}
}
}
impl Display for APTRepositoryPackageType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
APTRepositoryPackageType::Deb => write!(f, "deb"),
APTRepositoryPackageType::DebSrc => write!(f, "deb-src"),
}
}
}
#[api(
properties: {
Key: {
description: "Option key.",
type: String,
},
Values: {
description: "Option values.",
type: Array,
items: {
description: "Value.",
type: String,
},
},
},
)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")] // for consistency
/// Additional options for an APT repository.
/// Used for both single- and mutli-value options.
pub struct APTRepositoryOption {
/// Option key.
pub key: String,
/// Option value(s).
pub values: Vec<String>,
}
#[api(
properties: {
Types: {
description: "List of package types.",
type: Array,
items: {
type: APTRepositoryPackageType,
},
},
URIs: {
description: "List of repository URIs.",
type: Array,
items: {
description: "Repository URI.",
type: String,
},
},
Suites: {
description: "List of distributions.",
type: Array,
items: {
description: "Package distribution.",
type: String,
},
},
Components: {
description: "List of repository components.",
type: Array,
items: {
description: "Repository component.",
type: String,
},
},
Options: {
type: Array,
optional: true,
items: {
type: APTRepositoryOption,
},
},
Comment: {
description: "Associated comment.",
type: String,
optional: true,
},
FileType: {
type: APTRepositoryFileType,
},
Enabled: {
description: "Whether the repository is enabled or not.",
type: Boolean,
},
},
)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
/// Describes an APT repository.
pub struct APTRepository {
/// List of package types.
#[serde(skip_serializing_if = "Vec::is_empty")]
pub types: Vec<APTRepositoryPackageType>,
/// List of repository URIs.
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(rename = "URIs")]
pub uris: Vec<String>,
/// List of package distributions.
#[serde(skip_serializing_if = "Vec::is_empty")]
pub suites: Vec<String>,
/// List of repository components.
#[serde(skip_serializing_if = "Vec::is_empty")]
pub components: Vec<String>,
/// Additional options.
#[serde(skip_serializing_if = "Vec::is_empty")]
pub options: Vec<APTRepositoryOption>,
/// Associated comment.
#[serde(skip_serializing_if = "String::is_empty")]
pub comment: String,
/// Format of the defining file.
pub file_type: APTRepositoryFileType,
/// Whether the repository is enabled or not.
pub enabled: bool,
}
impl APTRepository {
/// Crates an empty repository.
pub fn new(file_type: APTRepositoryFileType) -> Self {
Self {
types: vec![],
uris: vec![],
suites: vec![],
components: vec![],
options: vec![],
comment: String::new(),
file_type,
enabled: true,
}
}
/// Changes the `enabled` flag and makes sure the `Enabled` option for
/// `APTRepositoryPackageType::Sources` repositories is updated too.
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
if self.file_type == APTRepositoryFileType::Sources {
let enabled_string = match enabled {
true => "true".to_string(),
false => "false".to_string(),
};
for option in self.options.iter_mut() {
if option.key == "Enabled" {
option.values = vec![enabled_string];
return;
}
}
self.options.push(APTRepositoryOption {
key: "Enabled".to_string(),
values: vec![enabled_string],
});
}
}
/// Makes sure that all basic properties of a repository are present and
/// not obviously invalid.
pub fn basic_check(&self) -> Result<(), Error> {
if self.types.is_empty() {
bail!("missing package type(s)");
}
if self.uris.is_empty() {
bail!("missing URI(s)");
}
if self.suites.is_empty() {
bail!("missing suite(s)");
}
for uri in self.uris.iter() {
if !uri.contains(':') || uri.len() < 3 {
bail!("invalid URI: '{}'", uri);
}
}
for suite in self.suites.iter() {
if !suite.ends_with('/') && self.components.is_empty() {
bail!("missing component(s)");
} else if suite.ends_with('/') && !self.components.is_empty() {
bail!("absolute suite '{}' does not allow component(s)", suite);
}
}
if self.file_type == APTRepositoryFileType::List {
if self.types.len() > 1 {
bail!("more than one package type");
}
if self.uris.len() > 1 {
bail!("more than one URI");
}
if self.suites.len() > 1 {
bail!("more than one suite");
}
}
Ok(())
}
/// Checks if the repository is the one referenced by the handle.
pub fn is_referenced_repository(
&self,
handle: APTRepositoryHandle,
product: &str,
suite: &str,
) -> bool {
let (package_type, handle_uris, component) = handle.info(product);
let mut found_uri = false;
for uri in self.uris.iter() {
let uri = uri.trim_end_matches('/');
found_uri = found_uri || handle_uris.iter().any(|handle_uri| handle_uri == uri);
}
self.types.contains(&package_type)
&& found_uri
// using contains would require a &String
&& self.suites.iter().any(|self_suite| self_suite == suite)
&& self.components.contains(&component)
}
/// Guess the origin from the repository's URIs.
///
/// Intended to be used as a fallback for get_cached_origin.
pub fn origin_from_uris(&self) -> Option<String> {
for uri in self.uris.iter() {
if let Some(host) = host_from_uri(uri) {
if host == "proxmox.com" || host.ends_with(".proxmox.com") {
return Some("Proxmox".to_string());
}
if host == "debian.org" || host.ends_with(".debian.org") {
return Some("Debian".to_string());
}
}
}
None
}
/// Get the `Origin:` value from a cached InRelease file.
pub fn get_cached_origin(&self) -> Result<Option<String>, Error> {
for uri in self.uris.iter() {
for suite in self.suites.iter() {
let file = in_release_filename(uri, suite);
if !file.exists() {
continue;
}
let raw = std::fs::read(&file)
.map_err(|err| format_err!("unable to read {:?} - {}", file, err))?;
let reader = BufReader::new(&*raw);
for line in reader.lines() {
let line =
line.map_err(|err| format_err!("unable to read {:?} - {}", file, err))?;
if let Some(value) = line.strip_prefix("Origin:") {
return Ok(Some(
value
.trim_matches(|c| char::is_ascii_whitespace(&c))
.to_string(),
));
}
}
}
}
Ok(None)
}
/// Writes a repository in the corresponding format followed by a blank.
///
/// Expects that `basic_check()` for the repository was successful.
pub fn write(&self, w: &mut dyn Write) -> Result<(), Error> {
match self.file_type {
APTRepositoryFileType::List => write_one_line(self, w),
APTRepositoryFileType::Sources => write_stanza(self, w),
}
}
}
/// Get the path to the cached InRelease file.
fn in_release_filename(uri: &str, suite: &str) -> PathBuf {
let mut path = PathBuf::from(&crate::config::get().dir_state);
path.push(&crate::config::get().dir_state_lists);
let filename = uri_to_filename(uri);
path.push(format!(
"{}_dists_{}_InRelease",
filename,
suite.replace('/', "_"), // e.g. for buster/updates
));
path
}
/// See APT's URItoFileName in contrib/strutl.cc
fn uri_to_filename(uri: &str) -> String {
let mut filename = uri;
if let Some(begin) = filename.find("://") {
filename = &filename[(begin + 3)..];
}
if uri.starts_with("http://") || uri.starts_with("https://") {
if let Some(begin) = filename.find('@') {
filename = &filename[(begin + 1)..];
}
}
// APT seems to only strip one final slash, so do the same
filename = filename.strip_suffix('/').unwrap_or(filename);
let encode_chars = "\\|{}[]<>\"^~_=!@#$%^&*";
let mut encoded = String::with_capacity(filename.len());
for b in filename.as_bytes().iter() {
if *b <= 0x20 || *b >= 0x7F || encode_chars.contains(*b as char) {
let mut hex = [0u8; 2];
// unwrap: we're hex-encoding a single byte into a 2-byte slice
hex::encode_to_slice(&[*b], &mut hex).unwrap();
let hex = unsafe { std::str::from_utf8_unchecked(&hex) };
encoded = format!("{}%{}", encoded, hex);
} else {
encoded.push(*b as char);
}
}
encoded.replace('/', "_")
}
/// Get the host part from a given URI.
fn host_from_uri(uri: &str) -> Option<&str> {
let host = uri.strip_prefix("http")?;
let host = host.strip_prefix('s').unwrap_or(host);
let mut host = host.strip_prefix("://")?;
if let Some(end) = host.find('/') {
host = &host[..end];
}
if let Some(begin) = host.find('@') {
host = &host[(begin + 1)..];
}
if let Some(end) = host.find(':') {
host = &host[..end];
}
Some(host)
}
/// Strips existing double quotes from the string first, and then adds double quotes at
/// the beginning and end if there is an ASCII whitespace in the `string`, which is not
/// escaped by `[]`.
fn quote_for_one_line(string: &str) -> String {
let mut add_quotes = false;
let mut wait_for_bracket = false;
// easier to just quote the whole string, so ignore pre-existing quotes
// currently, parsing removes them anyways, but being on the safe side is rather cheap
let string = string.replace('"', "");
for c in string.chars() {
if wait_for_bracket {
if c == ']' {
wait_for_bracket = false;
}
continue;
}
if char::is_ascii_whitespace(&c) {
add_quotes = true;
break;
}
if c == '[' {
wait_for_bracket = true;
}
}
match add_quotes {
true => format!("\"{}\"", string),
false => string,
}
}
/// Writes a repository in one-line format followed by a blank line.
///
/// Expects that `repo.file_type == APTRepositoryFileType::List`.
///
/// Expects that `basic_check()` for the repository was successful.
fn write_one_line(repo: &APTRepository, w: &mut dyn Write) -> Result<(), Error> {
if repo.file_type != APTRepositoryFileType::List {
bail!("not a .list repository");
}
if !repo.comment.is_empty() {
for line in repo.comment.lines() {
writeln!(w, "#{}", line)?;
}
}
if !repo.enabled {
write!(w, "# ")?;
}
write!(w, "{} ", repo.types[0])?;
if !repo.options.is_empty() {
write!(w, "[ ")?;
for option in repo.options.iter() {
let option = quote_for_one_line(&format!("{}={}", option.key, option.values.join(",")));
write!(w, "{} ", option)?;
}
write!(w, "] ")?;
};
write!(w, "{} ", quote_for_one_line(&repo.uris[0]))?;
write!(w, "{} ", quote_for_one_line(&repo.suites[0]))?;
writeln!(
w,
"{}",
repo.components
.iter()
.map(|comp| quote_for_one_line(comp))
.collect::<Vec<String>>()
.join(" ")
)?;
writeln!(w)?;
Ok(())
}
/// Writes a single stanza followed by a blank line.
///
/// Expects that `repo.file_type == APTRepositoryFileType::Sources`.
fn write_stanza(repo: &APTRepository, w: &mut dyn Write) -> Result<(), Error> {
if repo.file_type != APTRepositoryFileType::Sources {
bail!("not a .sources repository");
}
if !repo.comment.is_empty() {
for line in repo.comment.lines() {
writeln!(w, "#{}", line)?;
}
}
write!(w, "Types:")?;
repo.types
.iter()
.try_for_each(|package_type| write!(w, " {}", package_type))?;
writeln!(w)?;
writeln!(w, "URIs: {}", repo.uris.join(" "))?;
writeln!(w, "Suites: {}", repo.suites.join(" "))?;
if !repo.components.is_empty() {
writeln!(w, "Components: {}", repo.components.join(" "))?;
}
for option in repo.options.iter() {
writeln!(w, "{}: {}", option.key, option.values.join(" "))?;
}
writeln!(w)?;
Ok(())
}
#[test]
fn test_uri_to_filename() {
let filename = uri_to_filename("https://some_host/some/path");
assert_eq!(filename, "some%5fhost_some_path".to_string());
}

View File

@ -0,0 +1,273 @@
use std::fmt::Display;
use anyhow::{bail, Error};
use serde::{Deserialize, Serialize};
use crate::repositories::repository::{
APTRepository, APTRepositoryFileType, APTRepositoryPackageType,
};
use proxmox_schema::api;
#[api(
properties: {
handle: {
description: "Handle referencing a standard repository.",
type: String,
},
},
)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
/// Reference to a standard repository and configuration status.
pub struct APTStandardRepository {
/// Handle referencing a standard repository.
pub handle: APTRepositoryHandle,
/// Configuration status of the associated repository.
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<bool>,
/// Display name of the repository.
pub name: String,
/// Description of the repository.
pub description: String,
}
#[api]
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
/// Handles for Proxmox repositories.
pub enum APTRepositoryHandle {
/// The enterprise repository for production use.
Enterprise,
/// The repository that can be used without subscription.
NoSubscription,
/// The test repository.
Test,
/// Ceph Quincy repository.
CephQuincy,
/// Ceph Quincy test repository.
CephQuincyTest,
/// Ceph Pacific repository.
CephPacific,
/// Ceph Pacific test repository.
CephPacificTest,
/// Ceph Octoput repository.
CephOctopus,
/// Ceph Octoput test repository.
CephOctopusTest,
}
impl From<APTRepositoryHandle> for APTStandardRepository {
fn from(handle: APTRepositoryHandle) -> Self {
APTStandardRepository {
handle,
status: None,
name: handle.name(),
description: handle.description(),
}
}
}
impl TryFrom<&str> for APTRepositoryHandle {
type Error = Error;
fn try_from(string: &str) -> Result<Self, Error> {
match string {
"enterprise" => Ok(APTRepositoryHandle::Enterprise),
"no-subscription" => Ok(APTRepositoryHandle::NoSubscription),
"test" => Ok(APTRepositoryHandle::Test),
"ceph-quincy" => Ok(APTRepositoryHandle::CephQuincy),
"ceph-quincy-test" => Ok(APTRepositoryHandle::CephQuincyTest),
"ceph-pacific" => Ok(APTRepositoryHandle::CephPacific),
"ceph-pacific-test" => Ok(APTRepositoryHandle::CephPacificTest),
"ceph-octopus" => Ok(APTRepositoryHandle::CephOctopus),
"ceph-octopus-test" => Ok(APTRepositoryHandle::CephOctopusTest),
_ => bail!("unknown repository handle '{}'", string),
}
}
}
impl Display for APTRepositoryHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
APTRepositoryHandle::Enterprise => write!(f, "enterprise"),
APTRepositoryHandle::NoSubscription => write!(f, "no-subscription"),
APTRepositoryHandle::Test => write!(f, "test"),
APTRepositoryHandle::CephQuincy => write!(f, "ceph-quincy"),
APTRepositoryHandle::CephQuincyTest => write!(f, "ceph-quincy-test"),
APTRepositoryHandle::CephPacific => write!(f, "ceph-pacific"),
APTRepositoryHandle::CephPacificTest => write!(f, "ceph-pacific-test"),
APTRepositoryHandle::CephOctopus => write!(f, "ceph-octopus"),
APTRepositoryHandle::CephOctopusTest => write!(f, "ceph-octopus-test"),
}
}
}
impl APTRepositoryHandle {
/// Get the description for the repository.
pub fn description(self) -> String {
match self {
APTRepositoryHandle::Enterprise => {
"This is the default, stable, and recommended repository, available for all \
Proxmox subscription users."
}
APTRepositoryHandle::NoSubscription => {
"This is the recommended repository for testing and non-production use. \
Its packages are not as heavily tested and validated as the production ready \
enterprise repository. You don't need a subscription key to access this repository."
}
APTRepositoryHandle::Test => {
"This repository contains the latest packages and is primarily used for test labs \
and by developers to test new features."
}
APTRepositoryHandle::CephQuincy => {
"This repository holds the main Proxmox Ceph Quincy packages."
}
APTRepositoryHandle::CephQuincyTest => {
"This repository contains the Ceph Quincy packages before they are moved to the \
main repository."
}
APTRepositoryHandle::CephPacific => {
"This repository holds the main Proxmox Ceph Pacific packages."
}
APTRepositoryHandle::CephPacificTest => {
"This repository contains the Ceph Pacific packages before they are moved to the \
main repository."
}
APTRepositoryHandle::CephOctopus => {
"This repository holds the main Proxmox Ceph Octopus packages."
}
APTRepositoryHandle::CephOctopusTest => {
"This repository contains the Ceph Octopus packages before they are moved to the \
main repository."
}
}
.to_string()
}
/// Get the display name of the repository.
pub fn name(self) -> String {
match self {
APTRepositoryHandle::Enterprise => "Enterprise",
APTRepositoryHandle::NoSubscription => "No-Subscription",
APTRepositoryHandle::Test => "Test",
APTRepositoryHandle::CephQuincy => "Ceph Quincy",
APTRepositoryHandle::CephQuincyTest => "Ceph Quincy Test",
APTRepositoryHandle::CephPacific => "Ceph Pacific",
APTRepositoryHandle::CephPacificTest => "Ceph Pacific Test",
APTRepositoryHandle::CephOctopus => "Ceph Octopus",
APTRepositoryHandle::CephOctopusTest => "Ceph Octopus Test",
}
.to_string()
}
/// Get the standard file path for the repository referenced by the handle.
pub fn path(self, product: &str) -> String {
match self {
APTRepositoryHandle::Enterprise => {
format!("/etc/apt/sources.list.d/{}-enterprise.list", product)
}
APTRepositoryHandle::NoSubscription => "/etc/apt/sources.list".to_string(),
APTRepositoryHandle::Test => "/etc/apt/sources.list".to_string(),
APTRepositoryHandle::CephQuincy => "/etc/apt/sources.list.d/ceph.list".to_string(),
APTRepositoryHandle::CephQuincyTest => "/etc/apt/sources.list.d/ceph.list".to_string(),
APTRepositoryHandle::CephPacific => "/etc/apt/sources.list.d/ceph.list".to_string(),
APTRepositoryHandle::CephPacificTest => "/etc/apt/sources.list.d/ceph.list".to_string(),
APTRepositoryHandle::CephOctopus => "/etc/apt/sources.list.d/ceph.list".to_string(),
APTRepositoryHandle::CephOctopusTest => "/etc/apt/sources.list.d/ceph.list".to_string(),
}
}
/// Get package type, possible URIs and the component associated with the handle.
///
/// The first URI is the preferred one.
pub fn info(self, product: &str) -> (APTRepositoryPackageType, Vec<String>, String) {
match self {
APTRepositoryHandle::Enterprise => (
APTRepositoryPackageType::Deb,
match product {
"pve" => vec![
"https://enterprise.proxmox.com/debian/pve".to_string(),
"https://enterprise.proxmox.com/debian".to_string(),
],
_ => vec![format!("https://enterprise.proxmox.com/debian/{}", product)],
},
format!("{}-enterprise", product),
),
APTRepositoryHandle::NoSubscription => (
APTRepositoryPackageType::Deb,
match product {
"pve" => vec![
"http://download.proxmox.com/debian/pve".to_string(),
"http://download.proxmox.com/debian".to_string(),
],
_ => vec![format!("http://download.proxmox.com/debian/{}", product)],
},
format!("{}-no-subscription", product),
),
APTRepositoryHandle::Test => (
APTRepositoryPackageType::Deb,
match product {
"pve" => vec![
"http://download.proxmox.com/debian/pve".to_string(),
"http://download.proxmox.com/debian".to_string(),
],
_ => vec![format!("http://download.proxmox.com/debian/{}", product)],
},
format!("{}test", product),
),
APTRepositoryHandle::CephQuincy => (
APTRepositoryPackageType::Deb,
vec!["http://download.proxmox.com/debian/ceph-quincy".to_string()],
"main".to_string(),
),
APTRepositoryHandle::CephQuincyTest => (
APTRepositoryPackageType::Deb,
vec!["http://download.proxmox.com/debian/ceph-quincy".to_string()],
"test".to_string(),
),
APTRepositoryHandle::CephPacific => (
APTRepositoryPackageType::Deb,
vec!["http://download.proxmox.com/debian/ceph-pacific".to_string()],
"main".to_string(),
),
APTRepositoryHandle::CephPacificTest => (
APTRepositoryPackageType::Deb,
vec!["http://download.proxmox.com/debian/ceph-pacific".to_string()],
"test".to_string(),
),
APTRepositoryHandle::CephOctopus => (
APTRepositoryPackageType::Deb,
vec!["http://download.proxmox.com/debian/ceph-octopus".to_string()],
"main".to_string(),
),
APTRepositoryHandle::CephOctopusTest => (
APTRepositoryPackageType::Deb,
vec!["http://download.proxmox.com/debian/ceph-octopus".to_string()],
"test".to_string(),
),
}
}
/// Get the standard repository referenced by the handle.
///
/// An URI in the result is not '/'-terminated (under the assumption that no valid
/// product name is).
pub fn to_repository(self, product: &str, suite: &str) -> APTRepository {
let (package_type, uris, component) = self.info(product);
APTRepository {
types: vec![package_type],
uris: vec![uris.into_iter().next().unwrap()],
suites: vec![suite.to_string()],
components: vec![component],
options: vec![],
comment: String::new(),
file_type: APTRepositoryFileType::List,
enabled: true,
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256
Architectures: amd64
Codename: bullseye
Components: pvetest
Date: Mon, 28 Jun 2021 18:05:11 +0000
Label: Proxmox Debian Repository
Origin: Proxmox
Suite: stable
MD5Sum:
9bea8f79b808979720721f86da15b8c5 163424 pvetest/binary-amd64/Packages
a3286322ee6bfd3d5fec80d7c665f9c0 48621 pvetest/binary-amd64/Packages.gz
SHA1:
80fcafa4bf4a0c3c61c45d3b2fabc44d87772d42 163424 pvetest/binary-amd64/Packages
8ddbdacf5c6e4543300650e9abbfd91557ebae17 48621 pvetest/binary-amd64/Packages.gz
SHA256:
2b37f06ef01b4735db37a87f426c8042d2dce0fe3e571ba2a70259ef0d25d37b 163424 pvetest/binary-amd64/Packages
86cd183c6684d620dbea9419ac4585d2e6d35e2ecd83ccf3b63cd638d7fec93d 48621 pvetest/binary-amd64/Packages.gz
SHA512:
d2095e4a621159d339682a6340ba3fd9ad32ec2c5c25f39594f8abea803f5c90dfc8c161e1ab3bfd0ec0830ae00165ffb219e18be2e33a8be17c68b86d187ddf 163424 pvetest/binary-amd64/Packages
5af312c9b85252a071d24b434585579980ce28ee27073f076b78f7085b102d089026ded32ea9a00589f72e328362b42ee2efaf7282459d532be52e1766b8b007 48621 pvetest/binary-amd64/Packages.gz
-----BEGIN PGP SIGNATURE-----
iQIzBAEBCAAdFiEEKBOaL4ML1oR4oaAf3UujkX4jv1kFAmDaD1sACgkQ3UujkX4j
v1nT4A/9EwXscYVy3TVw/t1h21EdP/ADvwmJVWJbmhENh7FPxvRNnr9Q7r/4ZcOr
//gYAWm7e6DaiZEye5R7aH7BKKr16a5LOnXRv0nDWNSj50U/1B3KReGHFgRDRmyJ
EZ+bg99PzyRzsD5UCjeMdbFvOBLiX8nK430jDg4ccRNdhxzYcu3L7Ds1GiS8C/kO
bjB0hEEStqK0V4Lj5ZTrDOa+qoj9IS1N9Y4+loi+iEHRfF3q6vCXnqz/ZH53ese5
SDbKVaBNFX4zsXfeowLV2tqWkuYBgzjOBLNtSEezlWLm83hVQ5cr7MB7zt1yPAYy
V5+J7gIthKpJy9HE0DJ5qf+aekc/jt+tMVkrpwmwEmMIpyzY3Rh4tvpoyqC5ccqt
roll1XlnUjjWzPGJUh2W0SGOnvKLb16p/yqDALIYLDgzjnP/B6iserrekWa15Cvz
/HyffedsRbJ/JBDxJ54rV5KoPbRUsXkAp/fMZqOZu2wOxBHaTLqnHtHVJC9+pnef
dXeJixX0Bb34vZPbYRRByUrv6DueqlS0M27YIdLoO1KIvRzQL4OWfni6Fu9DF3M6
Ux1JF10Q/IwksQKXAyTgiGsStqnAGz13m0PJ+/swwzHK+Ktj7OoikKVnHZUkuljr
easVE4GoqJEwuKC6nI0Dosluy96wkPo5KecsUUdcSeToeaad07U=
=/mk7
-----END PGP SIGNATURE-----

View File

@ -0,0 +1,394 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256
Origin: Debian
Label: Debian
Suite: testing-updates
Codename: bullseye-updates
Date: Tue, 29 Jun 2021 08:07:21 UTC
Valid-Until: Tue, 06 Jul 2021 08:07:21 UTC
Acquire-By-Hash: yes
No-Support-for-Architecture-all: Packages
Architectures: all amd64 arm64 armel armhf i386 mips mips64el mipsel ppc64el s390x
Components: main contrib non-free
Description: Debian x.y Testing distribution Updates - Not Released
SHA256:
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-all
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-all.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-amd64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-amd64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-arm64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-arm64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-armel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-armel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-armhf
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-armhf.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-i386
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-i386.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-mips
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-mips.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-mips64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-mips64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-mipsel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-mipsel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-ppc64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-ppc64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-s390x
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-s390x.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-source
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-source.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-all
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-all.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-amd64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-amd64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-arm64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-arm64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-armel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-armel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-armhf
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-armhf.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-i386
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-i386.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-mips
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-mips.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-mips64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-mips64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-mipsel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-mipsel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-ppc64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-ppc64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-s390x
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-s390x.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-all/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-all/Packages.xz
68897647a7a7574bc3cbf2752a8197b599a92bb51594114dc309f189a57d2d76 112 contrib/binary-all/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-amd64/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-amd64/Packages.xz
1b82761373d3072d18c5da66c06c445463e3a96413df6801d67bc8f593994b4c 114 contrib/binary-amd64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-arm64/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-arm64/Packages.xz
90a6100dbdb528f71c742453100ed389bf1889f230f97850ad447f5f1842ee66 114 contrib/binary-arm64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-armel/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-armel/Packages.xz
160791b0d348565711501f5a64731aa163f4ffc264adf0941783b126edacd0fe 114 contrib/binary-armel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-armhf/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-armhf/Packages.xz
f9c32c87b588d6cebbcc15ca1019e6079cd70d0bb470c349948044ac1d708ca9 114 contrib/binary-armhf/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-i386/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-i386/Packages.xz
065ff088c1eb921afe428d9b9995c10ded4e1460eee96b0d18946f40a581d051 113 contrib/binary-i386/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-mips/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-mips/Packages.xz
371aa544d22e9bffa4743e38f48fcc39279371c17ecfc11506ab5e969d4209e9 113 contrib/binary-mips/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-mips64el/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-mips64el/Packages.xz
952b96752ea34a4236ed7d8e96931db30f5f1152be9e979421cbec33e1281d93 117 contrib/binary-mips64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-mipsel/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-mipsel/Packages.xz
1269cc174396d41f61bee7ef18394c0cd55062c331392184a228f80e10a92e50 115 contrib/binary-mipsel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-ppc64el/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-ppc64el/Packages.xz
9ceedee057d170036270046025f9763a90be0a0f622af390aacc5dc917cbe2aa 116 contrib/binary-ppc64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-s390x/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-s390x/Packages.xz
bb3f0c6a788afab2b7996008c2b5306a374dc20b34873ea4b0ce267aaaea533c 114 contrib/binary-s390x/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-all/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-all/Packages.xz
68897647a7a7574bc3cbf2752a8197b599a92bb51594114dc309f189a57d2d76 112 contrib/debian-installer/binary-all/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-amd64/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-amd64/Packages.xz
1b82761373d3072d18c5da66c06c445463e3a96413df6801d67bc8f593994b4c 114 contrib/debian-installer/binary-amd64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-arm64/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-arm64/Packages.xz
90a6100dbdb528f71c742453100ed389bf1889f230f97850ad447f5f1842ee66 114 contrib/debian-installer/binary-arm64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-armel/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-armel/Packages.xz
160791b0d348565711501f5a64731aa163f4ffc264adf0941783b126edacd0fe 114 contrib/debian-installer/binary-armel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-armhf/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-armhf/Packages.xz
f9c32c87b588d6cebbcc15ca1019e6079cd70d0bb470c349948044ac1d708ca9 114 contrib/debian-installer/binary-armhf/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-i386/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-i386/Packages.xz
065ff088c1eb921afe428d9b9995c10ded4e1460eee96b0d18946f40a581d051 113 contrib/debian-installer/binary-i386/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-mips/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-mips/Packages.xz
371aa544d22e9bffa4743e38f48fcc39279371c17ecfc11506ab5e969d4209e9 113 contrib/debian-installer/binary-mips/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-mips64el/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-mips64el/Packages.xz
952b96752ea34a4236ed7d8e96931db30f5f1152be9e979421cbec33e1281d93 117 contrib/debian-installer/binary-mips64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-mipsel/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-mipsel/Packages.xz
1269cc174396d41f61bee7ef18394c0cd55062c331392184a228f80e10a92e50 115 contrib/debian-installer/binary-mipsel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-ppc64el/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-ppc64el/Packages.xz
9ceedee057d170036270046025f9763a90be0a0f622af390aacc5dc917cbe2aa 116 contrib/debian-installer/binary-ppc64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-s390x/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-s390x/Packages.xz
bb3f0c6a788afab2b7996008c2b5306a374dc20b34873ea4b0ce267aaaea533c 114 contrib/debian-installer/binary-s390x/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/i18n/Translation-en
d3dda84eb03b9738d118eb2be78e246106900493c0ae07819ad60815134a8058 14 contrib/i18n/Translation-en.bz2
66ff0b3e39b97b69ae41026692402554a34da7523806e9484dadaa201d874826 115 contrib/source/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/source/Sources
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/source/Sources.xz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-all
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-all.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-amd64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-amd64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-arm64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-arm64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-armel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-armel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-armhf
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-armhf.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-i386
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-i386.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-mips
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-mips.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-mips64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-mips64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-mipsel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-mipsel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-ppc64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-ppc64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-s390x
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-s390x.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-source
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-source.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-all
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-all.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-amd64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-amd64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-arm64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-arm64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-armel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-armel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-armhf
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-armhf.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-i386
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-i386.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-mips
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-mips.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-mips64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-mips64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-mipsel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-mipsel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-ppc64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-ppc64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-s390x
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-s390x.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-all/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/binary-all/Packages.xz
4ec10e1952b6b67f18ba86e49a9fdadae2702fc8dd9caf81fce3fa67b26933c1 109 main/binary-all/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-amd64/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/binary-amd64/Packages.xz
11235baea1cc51f728b67e6a72b440eb5c45476bccd33d4c93308bc3ac5aebb3 111 main/binary-amd64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-arm64/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/binary-arm64/Packages.xz
f9638553772bb2f185419f349d1a0835ecc23074a1c867f64ed81b43b79cec85 111 main/binary-arm64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-armel/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/binary-armel/Packages.xz
e6dc2d69357d199629d876d48880f3fe4675aa3a7174ac868a58d7fa3853a9d3 111 main/binary-armel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-armhf/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/binary-armhf/Packages.xz
93df18bef47710a17488b390419821583b4894ef1639a7d89657e422bf5fc915 111 main/binary-armhf/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-i386/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/binary-i386/Packages.xz
a3458e3b58f39105b96c2ec4c92a977c398f53aa29f7838da3c537de78546a6a 110 main/binary-i386/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-mips/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/binary-mips/Packages.xz
027758abbd72b9bb64fd321e4d5c5f6288fa20e376c11f4ecf8bf4ade3acb47c 110 main/binary-mips/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-mips64el/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/binary-mips64el/Packages.xz
6fbea5916d47ff86a4b93e9643f078b59dcfaf3a25c2f5f2ba4e5416ce4c8117 114 main/binary-mips64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-mipsel/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/binary-mipsel/Packages.xz
f160fb8cee5639caf0f75a5c4aab1fff1262f824a9621fb45ca15dfa698baf03 112 main/binary-mipsel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-ppc64el/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/binary-ppc64el/Packages.xz
014cead9abbc4cce0ba6e078dce34a8d1a56e6b16b7bee1a463fd9f67eed2c33 113 main/binary-ppc64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-s390x/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/binary-s390x/Packages.xz
78b704e8437b918152687d47061e2211a5119015e2a13a0d6a46d31dcb32491b 111 main/binary-s390x/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-all/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-all/Packages.xz
4ec10e1952b6b67f18ba86e49a9fdadae2702fc8dd9caf81fce3fa67b26933c1 109 main/debian-installer/binary-all/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-amd64/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-amd64/Packages.xz
11235baea1cc51f728b67e6a72b440eb5c45476bccd33d4c93308bc3ac5aebb3 111 main/debian-installer/binary-amd64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-arm64/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-arm64/Packages.xz
f9638553772bb2f185419f349d1a0835ecc23074a1c867f64ed81b43b79cec85 111 main/debian-installer/binary-arm64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-armel/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-armel/Packages.xz
e6dc2d69357d199629d876d48880f3fe4675aa3a7174ac868a58d7fa3853a9d3 111 main/debian-installer/binary-armel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-armhf/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-armhf/Packages.xz
93df18bef47710a17488b390419821583b4894ef1639a7d89657e422bf5fc915 111 main/debian-installer/binary-armhf/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-i386/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-i386/Packages.xz
a3458e3b58f39105b96c2ec4c92a977c398f53aa29f7838da3c537de78546a6a 110 main/debian-installer/binary-i386/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-mips/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-mips/Packages.xz
027758abbd72b9bb64fd321e4d5c5f6288fa20e376c11f4ecf8bf4ade3acb47c 110 main/debian-installer/binary-mips/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-mips64el/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-mips64el/Packages.xz
6fbea5916d47ff86a4b93e9643f078b59dcfaf3a25c2f5f2ba4e5416ce4c8117 114 main/debian-installer/binary-mips64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-mipsel/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-mipsel/Packages.xz
f160fb8cee5639caf0f75a5c4aab1fff1262f824a9621fb45ca15dfa698baf03 112 main/debian-installer/binary-mipsel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-ppc64el/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-ppc64el/Packages.xz
014cead9abbc4cce0ba6e078dce34a8d1a56e6b16b7bee1a463fd9f67eed2c33 113 main/debian-installer/binary-ppc64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-s390x/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-s390x/Packages.xz
78b704e8437b918152687d47061e2211a5119015e2a13a0d6a46d31dcb32491b 111 main/debian-installer/binary-s390x/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/i18n/Translation-en
d3dda84eb03b9738d118eb2be78e246106900493c0ae07819ad60815134a8058 14 main/i18n/Translation-en.bz2
008e62f7c379b27d3f00fe8ea8eaa139809f11900ca89d6617ccf934a9024142 112 main/source/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/source/Sources
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/source/Sources.xz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-all
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-all.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-amd64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-amd64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-arm64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-arm64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-armel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-armel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-armhf
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-armhf.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-i386
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-i386.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-mips
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-mips.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-mips64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-mips64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-mipsel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-mipsel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-ppc64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-ppc64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-s390x
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-s390x.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-source
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-source.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-all
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-all.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-amd64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-amd64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-arm64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-arm64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-armel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-armel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-armhf
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-armhf.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-i386
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-i386.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-mips
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-mips.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-mips64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-mips64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-mipsel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-mipsel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-ppc64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-ppc64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-s390x
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-s390x.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-all/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-all/Packages.xz
28dec0023644dd6907ab629efa820fdd61b10c19eb43ddf0d8737c5057e0c7b4 113 non-free/binary-all/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-amd64/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-amd64/Packages.xz
1b520c87f6a798699fc39e1d75ebac7e78fdd4a201b18426109f0f338f18e366 115 non-free/binary-amd64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-arm64/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-arm64/Packages.xz
9d15a08c7396b78ed595c22f5d1cce574d4cf0dcda3d5458722270dce0ddf276 115 non-free/binary-arm64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-armel/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-armel/Packages.xz
c19938ccb9743e0318b2bb3e7871a2347d74afadfd23e23f63465ed31d01e2db 115 non-free/binary-armel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-armhf/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-armhf/Packages.xz
88c4a76de56dd43f88d925c471093ace5e7aac17a6d6a8b01135fb3cf6a1d80f 115 non-free/binary-armhf/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-i386/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-i386/Packages.xz
bfab31be4f95d8f8eec40f7e7dfe7caad494a1496945989a0b5936d24125a711 114 non-free/binary-i386/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-mips/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-mips/Packages.xz
37a9b6efa8e5b0f9ee74f3202db161c209fa066830268ea30b206d8b1f9052c1 114 non-free/binary-mips/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-mips64el/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-mips64el/Packages.xz
d50cbb62225425908c57af80d918ccc99859ba4803be62b44da8d8159eb86847 118 non-free/binary-mips64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-mipsel/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-mipsel/Packages.xz
a30e01cfad8b7f5e5f7115787590a2a3342ddea51857ccb230180e555b9c68a4 116 non-free/binary-mipsel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-ppc64el/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-ppc64el/Packages.xz
33da686a19b40e79ba83a7daf8d863010c36ff9f6d063ecc698a91d3cf626f53 117 non-free/binary-ppc64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-s390x/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-s390x/Packages.xz
acc91e4fa6121d24aba8b1e8f39cffde4512bb549044cbf88f998cd4dc0423df 115 non-free/binary-s390x/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-all/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-all/Packages.xz
28dec0023644dd6907ab629efa820fdd61b10c19eb43ddf0d8737c5057e0c7b4 113 non-free/debian-installer/binary-all/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-amd64/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-amd64/Packages.xz
1b520c87f6a798699fc39e1d75ebac7e78fdd4a201b18426109f0f338f18e366 115 non-free/debian-installer/binary-amd64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-arm64/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-arm64/Packages.xz
9d15a08c7396b78ed595c22f5d1cce574d4cf0dcda3d5458722270dce0ddf276 115 non-free/debian-installer/binary-arm64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-armel/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-armel/Packages.xz
c19938ccb9743e0318b2bb3e7871a2347d74afadfd23e23f63465ed31d01e2db 115 non-free/debian-installer/binary-armel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-armhf/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-armhf/Packages.xz
88c4a76de56dd43f88d925c471093ace5e7aac17a6d6a8b01135fb3cf6a1d80f 115 non-free/debian-installer/binary-armhf/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-i386/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-i386/Packages.xz
bfab31be4f95d8f8eec40f7e7dfe7caad494a1496945989a0b5936d24125a711 114 non-free/debian-installer/binary-i386/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-mips/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-mips/Packages.xz
37a9b6efa8e5b0f9ee74f3202db161c209fa066830268ea30b206d8b1f9052c1 114 non-free/debian-installer/binary-mips/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-mips64el/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-mips64el/Packages.xz
d50cbb62225425908c57af80d918ccc99859ba4803be62b44da8d8159eb86847 118 non-free/debian-installer/binary-mips64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-mipsel/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-mipsel/Packages.xz
a30e01cfad8b7f5e5f7115787590a2a3342ddea51857ccb230180e555b9c68a4 116 non-free/debian-installer/binary-mipsel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-ppc64el/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-ppc64el/Packages.xz
33da686a19b40e79ba83a7daf8d863010c36ff9f6d063ecc698a91d3cf626f53 117 non-free/debian-installer/binary-ppc64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-s390x/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-s390x/Packages.xz
acc91e4fa6121d24aba8b1e8f39cffde4512bb549044cbf88f998cd4dc0423df 115 non-free/debian-installer/binary-s390x/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/i18n/Translation-en
d3dda84eb03b9738d118eb2be78e246106900493c0ae07819ad60815134a8058 14 non-free/i18n/Translation-en.bz2
75a93381931d1caa336f5674cd3cb576876aa87c13d0c66dd6be20046df7a1b1 116 non-free/source/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/source/Sources
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/source/Sources.xz
-----BEGIN PGP SIGNATURE-----
iQIzBAEBCAAdFiEEFukLP99l7eOqfzI8BO5yN7fUU+wFAmDa1L8ACgkQBO5yN7fU
U+xlnw/+N8x7oU6dO9Tofgv3aFmHVOOyzbO0a6o1xkweoSxixcZVebSEv0o0tOpg
FIHSc47OvfQGI4HrSNAwO3iyNHVqY0gxEaXr9kOeIC/4v8Vi8FYOsHI3TQ21ZUIS
ZBVPAHi5gB0TztO5MArDXiJuo6EY/g810gPsQrp3op3Xn6EhSB9Bi2Z/Bi/rP2Pk
D/vq+JqztcJ7ufsS0v17Z24gYdR4MZlTi/CLrQeuqkqcWdx0lLa/YX9GTMXK8YTm
j8O0dfJWqtgdq/0vN0zez9r49rkxszp8q7lF1+U4QvGxstUdMtZzSVVM0qnE2fys
4lb2J+Qamv+ycqflWccINQvVbHI8TruthzGOi7hn5OKDVE84Jd2etAczd8eKvAIa
eckMCMI0LzqbuMYWglVIUCFbUDNrc+UnLT022k+l3mkV9zpQEmkDRvPHZRXxPCaF
unETH3IsJ+g+HK8OskgJoFcV4efHRW3za20CNhvGExf/Yl0/UmQl2OO1DeK9JhBQ
LYGyxKNXSsutdN1iDVGmNl6xwuwN5JRf65LHrO4VSJ+wF3fVpri7xqrg7+irMccd
nC7UNT064HIREumseM0PqkFx3Rw0Ysiv/znsCW85YOyc3p8xBDlzBlJVgiAMZsmx
gkTOZRzHbZ34a4VI5vpN2Cf7qAAN+kuAtwsBE0905UnpLDIyYraJAjMEAQEIAB0W
IQQBRtxtSgspFL3tNNtkis/WIvPROAUCYNrU2gAKCRBkis/WIvPROMtID/4+d48X
MbZYDh8KSjz66qt/juhsTKeGQlRKxnbwxUKRRnhXrwd4MmHsUDqJQk3br4B8TqAp
8V7H8ZT7hhcvMD8ghj4Sw/dG8BPLXntDLxsSRnb5oMZxvwfeeH1smnTu6WOQUgzr
J3p9CEBiR8VBm3hy10QNPepkqhggPdZvGM6N8G7OBSteFwVCRsA6MW8A+SN24t9g
qp7898+iebhRCxhCXNiP6pMXI67dZtZbCWKSPivFC7IoCedSlOPmmtXgJZo3fhEt
vr09VKSyr7+w8IBpRZlhElq2WFHPm5TnK13iGmxhKR0msApj4RciVH/nFLLaRUHC
05qXmA/4vDgrlzIme14sTHiaEVHeFk4DYWxae69MbbppxrYhv488jHoeIcUxJlTw
ydi+TmmMwtpZdugYtAHKBxvHtyxiB5d4DHRykkwvCdv0OWPja6GDPDTMO1ix+86E
ruidHOr6g4maV4EvjqqXkE2MFYAHxVaMkSz2jPHtEKlneusLSdnYgghVCByf4xbl
FTho8AKy88F4qT0oDNsedrlv9OZe7eif4hi9r6NabVUcI+4wAKjMV40mg5EpXR/F
otGmeLMo4LO9kdnHuMhFNdlNJCOdBy2czJV/f7G3KprPXj4TSgDV3jIimwnGf4MQ
6OIWLDlKkCemDssg/bCLX8Z8GacZAWvI+YySPQ==
=dXdg
-----END PGP SIGNATURE-----

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,426 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256
Origin: Debian
Label: Debian-Security
Suite: testing-security
Codename: bullseye-security
Date: Tue, 29 Jun 2021 11:21:24 UTC
Valid-Until: Tue, 06 Jul 2021 11:21:24 UTC
Acquire-By-Hash: yes
Architectures: amd64 arm64 armel armhf i386 mips64el mipsel ppc64el s390x
Components: updates/main updates/contrib updates/non-free
Description: Debian x.y Testing - Security Updates - Not Released
SHA256:
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-amd64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-amd64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-arm64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-arm64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-armel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-armel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-armhf
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-armhf.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-i386
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-i386.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-mips
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-mips.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-mips64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-mips64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-mipsel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-mipsel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-ppc64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-ppc64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-s390x
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-s390x.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-source
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-source.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-amd64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-amd64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-arm64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-arm64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-armel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-armel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-armhf
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-armhf.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-i386
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-i386.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-mips
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-mips.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-mips64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-mips64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-mipsel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-mipsel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-ppc64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-ppc64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-s390x
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-s390x.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-all/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/binary-all/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-all/Packages.xz
dad6f4ef22f149e992d811397605c205e6200f0f997310befb7b30281fb20529 130 contrib/binary-all/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-amd64/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/binary-amd64/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-amd64/Packages.xz
95155862fd275db390e032008b507c6de9f5c88b6675067a2213b733ff65a71b 132 contrib/binary-amd64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-arm64/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/binary-arm64/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-arm64/Packages.xz
dc44f7c5477b840abd146ba41d3c1295a078cabdd0b8a5c3e8de1f8981bb4cab 132 contrib/binary-arm64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-armel/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/binary-armel/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-armel/Packages.xz
518a30fe51451f86b09975c1d86fa8eaae5f946f016acdbf4c9c84629deb6323 132 contrib/binary-armel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-armhf/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/binary-armhf/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-armhf/Packages.xz
a786baa4947efa2435a6fe9aa388ac9e0b1f44f058ea032c3f5717eb8abb69b2 132 contrib/binary-armhf/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-i386/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/binary-i386/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-i386/Packages.xz
0c21f39be33458944fdc075f9a56a29101e606f36169a4a8e915768f5213f8c1 131 contrib/binary-i386/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-mips64el/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/binary-mips64el/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-mips64el/Packages.xz
312b1b4faf7b4353c795af321e782b07c1cb0e06b1308e17f8a28b5309604616 135 contrib/binary-mips64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-mipsel/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/binary-mipsel/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-mipsel/Packages.xz
770d42f09c2fe61ad419b63a36995e5824cb50d95244aa92aadcbe5b699443b6 133 contrib/binary-mipsel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-ppc64el/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/binary-ppc64el/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-ppc64el/Packages.xz
41441fd2370acf8ecc41cc58113534747d97f6484c8f02f28310113f302428ab 134 contrib/binary-ppc64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-s390x/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/binary-s390x/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-s390x/Packages.xz
991da76f3ef2b5ac24b81475e9de36464b139b435335cd98d073425bae2f7b59 132 contrib/binary-s390x/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-all/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-all/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-all/Packages.xz
dad6f4ef22f149e992d811397605c205e6200f0f997310befb7b30281fb20529 130 contrib/debian-installer/binary-all/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-amd64/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-amd64/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-amd64/Packages.xz
95155862fd275db390e032008b507c6de9f5c88b6675067a2213b733ff65a71b 132 contrib/debian-installer/binary-amd64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-arm64/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-arm64/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-arm64/Packages.xz
dc44f7c5477b840abd146ba41d3c1295a078cabdd0b8a5c3e8de1f8981bb4cab 132 contrib/debian-installer/binary-arm64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-armel/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-armel/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-armel/Packages.xz
518a30fe51451f86b09975c1d86fa8eaae5f946f016acdbf4c9c84629deb6323 132 contrib/debian-installer/binary-armel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-armhf/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-armhf/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-armhf/Packages.xz
a786baa4947efa2435a6fe9aa388ac9e0b1f44f058ea032c3f5717eb8abb69b2 132 contrib/debian-installer/binary-armhf/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-i386/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-i386/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-i386/Packages.xz
0c21f39be33458944fdc075f9a56a29101e606f36169a4a8e915768f5213f8c1 131 contrib/debian-installer/binary-i386/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-mips64el/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-mips64el/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-mips64el/Packages.xz
312b1b4faf7b4353c795af321e782b07c1cb0e06b1308e17f8a28b5309604616 135 contrib/debian-installer/binary-mips64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-mipsel/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-mipsel/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-mipsel/Packages.xz
770d42f09c2fe61ad419b63a36995e5824cb50d95244aa92aadcbe5b699443b6 133 contrib/debian-installer/binary-mipsel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-ppc64el/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-ppc64el/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-ppc64el/Packages.xz
41441fd2370acf8ecc41cc58113534747d97f6484c8f02f28310113f302428ab 134 contrib/debian-installer/binary-ppc64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-s390x/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-s390x/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-s390x/Packages.xz
991da76f3ef2b5ac24b81475e9de36464b139b435335cd98d073425bae2f7b59 132 contrib/debian-installer/binary-s390x/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/i18n/Translation-en
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/i18n/Translation-en.xz
47fd677ef64107683fd0b8bac29352c4a047b2d7310462753ca47eeb3096ee3b 133 contrib/source/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/source/Sources
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/source/Sources.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/source/Sources.xz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-amd64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-amd64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-arm64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-arm64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-armel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-armel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-armhf
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-armhf.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-i386
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-i386.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-mips
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-mips.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-mips64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-mips64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-mipsel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-mipsel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-ppc64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-ppc64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-s390x
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-s390x.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-source
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-source.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-amd64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-amd64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-arm64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-arm64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-armel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-armel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-armhf
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-armhf.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-i386
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-i386.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-mips
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-mips.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-mips64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-mips64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-mipsel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-mipsel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-ppc64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-ppc64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-s390x
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-s390x.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-all/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/binary-all/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/binary-all/Packages.xz
0a4d9c1090736291f4a41265e239709235907edec6c725bedccfc41ce1e52a05 127 main/binary-all/Release
bbae2caedf9d88a82df30e5af6ec0eb493604d347fd98b7a9749568c304df22a 1928 main/binary-amd64/Packages
ac756c56a08c59f831db4d6c17d480b21f2704b3d51961fa2ff284a0a82a769a 763 main/binary-amd64/Packages.gz
16278569504f0663d817c45332e2034d8343107e1c445cde9994f6136b3fcb00 812 main/binary-amd64/Packages.xz
26279f9ed5f2d43aa257b16534a3e1f31e83c4d4148a6c6450b6f08152659a8c 129 main/binary-amd64/Release
89a8e73a35cc1ff8a08b9ca35e097823dadf2a9d31ae5375f5f7b7f2fe4e2b65 1928 main/binary-arm64/Packages
a8c3b768b8b335fdc885c55601c5132f4d3a917b2cfd687d2e89ea185866ee1d 762 main/binary-arm64/Packages.gz
3c2ce72e75afb59119f1e2f83516ab4d727ed74286fbdc799185eb0fb28e2e55 808 main/binary-arm64/Packages.xz
bd8c91fe3be0b96a19b100325c41dc9fd49623d2df10436c9db044a276fac392 129 main/binary-arm64/Release
19f9cc6b15e0c8e42af1b02c29419d3e863a3511400ec9d2a5d5c020df19abe4 1928 main/binary-armel/Packages
1b96bcd0d2e611afd97c3a7d46781c8ef2fdba6c1eb45aaf6e1d1787c637eb25 763 main/binary-armel/Packages.gz
960909e14220da768efa538f27cd2ae2bdbe3e17ea82ef03506b22f0fb8f4179 804 main/binary-armel/Packages.xz
2e8e8045b28d0ad3bdae7e86e6ba22c103a7775af0e7ae766e482b59b559371b 129 main/binary-armel/Release
d14cd548e7903c2a734afeea287bc8a6fc1e0c84b570b0e04af074e485e4cf02 1928 main/binary-armhf/Packages
99eeb65ade7992b28a12b558ccd3ef443aad85b36cf7cd21989993f016ad736e 764 main/binary-armhf/Packages.gz
ae2120dcd2dd3c6c616984dd206745f2b895e2e98968dea1c4a7e6041de9a644 808 main/binary-armhf/Packages.xz
3dcb7a44da260ad8a0d0ba7e25233f7c2b35722c565b6d435fd0d493b018d355 129 main/binary-armhf/Release
e51c53b7af69fed94ba0b0f947fead354e327f923b00263f919cc790a9426dd3 1922 main/binary-i386/Packages
4cfcd11b768856e7f1de9d3bd985880c8cac118d4e9128831032f1fe1e75c9b2 763 main/binary-i386/Packages.gz
6e863d5d686d7d35a77935bbcd92e3c31ddc2cbd97a50cdfa939d628f1080283 808 main/binary-i386/Packages.xz
e9fc3062aeec4399fab10a52f6e7e81a887ce6fec27efb40974621b13d973c9f 128 main/binary-i386/Release
3b08feaf135dc663036881aeed987dc3aba8652d740f276feba59eac8e293b3d 1946 main/binary-mips64el/Packages
dcce951cd1bef0b08fa7b464b37d805de6a9b59bce36472f68b77535e565f317 759 main/binary-mips64el/Packages.gz
9b909a22910a80c29dc6d80caa246af17413784049d600e415cd77543b06bfe9 812 main/binary-mips64el/Packages.xz
664edd8fcb5b7b5c163cbc0ad06cb93882b63c2ec67f41ac5b3b1c2c042e5a44 132 main/binary-mips64el/Release
d2823a214b46aa9cb76340cc4b0cc79e331f204ed5ff87a3161fa480743c219f 1934 main/binary-mipsel/Packages
f8c288471b2c6c2fc27100d4d380057f43fd5a8bd335e74be9e63e48244e951e 764 main/binary-mipsel/Packages.gz
118034c37ac931ec11e26a51e1c48014374181f64bf90b001ce661534ba0157d 808 main/binary-mipsel/Packages.xz
29fff1f8b8a24fb9558402acc8f62d81152d9acfaa5e93b84a743c1305c8480b 130 main/binary-mipsel/Release
b89f554d34172357ce4986fe329b9d76e409c8421cfcab9390e6e9c616b47ed6 1940 main/binary-ppc64el/Packages
800486b18b08eb73d535d6d36ef3e8782595765f4504ca8ce769b02e341df5d5 762 main/binary-ppc64el/Packages.gz
d9c8e0c73dd37d02a2159c0e120e620489b324bb5c18e9dae15364d26558fbb2 812 main/binary-ppc64el/Packages.xz
962bb7d7a66b74d348f41c770a8d67d45ac44ebaddebdaa221d9cd97650b47f3 131 main/binary-ppc64el/Release
25f871341e67f34847523c31d0bd302aa00eb394ef3b47c83153f3a3fb1bc2f1 1928 main/binary-s390x/Packages
4bb0ea6e9fcafc492bf50d5ea24783dbdae1924d5803c792f94373521e6dfc99 764 main/binary-s390x/Packages.gz
03d65434978fbf391da00927b0b587d1e5364cb254f03391efe02f29fffea462 808 main/binary-s390x/Packages.xz
9f779b6d069ad34c2d19613aef3d4292ed4bde866ceef7dd2c54caa1f61859a1 129 main/binary-s390x/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-all/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/debian-installer/binary-all/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-all/Packages.xz
0a4d9c1090736291f4a41265e239709235907edec6c725bedccfc41ce1e52a05 127 main/debian-installer/binary-all/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-amd64/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/debian-installer/binary-amd64/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-amd64/Packages.xz
26279f9ed5f2d43aa257b16534a3e1f31e83c4d4148a6c6450b6f08152659a8c 129 main/debian-installer/binary-amd64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-arm64/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/debian-installer/binary-arm64/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-arm64/Packages.xz
bd8c91fe3be0b96a19b100325c41dc9fd49623d2df10436c9db044a276fac392 129 main/debian-installer/binary-arm64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-armel/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/debian-installer/binary-armel/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-armel/Packages.xz
2e8e8045b28d0ad3bdae7e86e6ba22c103a7775af0e7ae766e482b59b559371b 129 main/debian-installer/binary-armel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-armhf/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/debian-installer/binary-armhf/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-armhf/Packages.xz
3dcb7a44da260ad8a0d0ba7e25233f7c2b35722c565b6d435fd0d493b018d355 129 main/debian-installer/binary-armhf/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-i386/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/debian-installer/binary-i386/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-i386/Packages.xz
e9fc3062aeec4399fab10a52f6e7e81a887ce6fec27efb40974621b13d973c9f 128 main/debian-installer/binary-i386/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-mips64el/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/debian-installer/binary-mips64el/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-mips64el/Packages.xz
664edd8fcb5b7b5c163cbc0ad06cb93882b63c2ec67f41ac5b3b1c2c042e5a44 132 main/debian-installer/binary-mips64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-mipsel/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/debian-installer/binary-mipsel/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-mipsel/Packages.xz
29fff1f8b8a24fb9558402acc8f62d81152d9acfaa5e93b84a743c1305c8480b 130 main/debian-installer/binary-mipsel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-ppc64el/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/debian-installer/binary-ppc64el/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-ppc64el/Packages.xz
962bb7d7a66b74d348f41c770a8d67d45ac44ebaddebdaa221d9cd97650b47f3 131 main/debian-installer/binary-ppc64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-s390x/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/debian-installer/binary-s390x/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-s390x/Packages.xz
9f779b6d069ad34c2d19613aef3d4292ed4bde866ceef7dd2c54caa1f61859a1 129 main/debian-installer/binary-s390x/Release
b54ef4560b294e63b9b4552e1e0828060c8c5038db75d25a9f8bdd1414205feb 1190 main/i18n/Translation-en
05b17d5fe2084e0d21081a714c09751aa17c147d01e3148bd42d48643e54f3e9 596 main/i18n/Translation-en.xz
545c498daba5eefe133c68836f2086ebbc6027e496a09af436609e0e0f3130c0 130 main/source/Release
194afb538a6393cbc3515804a8577b6d9f939e4dfa28b2b56b5b24c68799e299 1210 main/source/Sources
2253816c6e56c03e9dd8b7d11780ea40c2959c81327264257d60938858e7c62d 727 main/source/Sources.gz
82bff31f0c30a1c0cb5c5691430a19a52ec855d69c101d7fb927e7fc38b5e9b3 780 main/source/Sources.xz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-amd64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-amd64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-arm64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-arm64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-armel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-armel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-armhf
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-armhf.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-i386
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-i386.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-mips
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-mips.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-mips64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-mips64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-mipsel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-mipsel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-ppc64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-ppc64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-s390x
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-s390x.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-source
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-source.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-amd64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-amd64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-arm64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-arm64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-armel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-armel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-armhf
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-armhf.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-i386
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-i386.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-mips
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-mips.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-mips64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-mips64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-mipsel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-mipsel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-ppc64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-ppc64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-s390x
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-s390x.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-all/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/binary-all/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-all/Packages.xz
09692a805a856602cecf048c99e3371a98d391bbde03f6f3b1b348fd14988f57 131 non-free/binary-all/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-amd64/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/binary-amd64/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-amd64/Packages.xz
62f830e8bbe3cdae15bd5eb6a49a7eab262e3b4f1ea05dd15334d150e44d2067 133 non-free/binary-amd64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-arm64/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/binary-arm64/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-arm64/Packages.xz
d9bfd44bcf537cce90d40ea23f58b87c5b9a648bf72fdeaeb1d15cd797db1575 133 non-free/binary-arm64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-armel/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/binary-armel/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-armel/Packages.xz
7bf07a333833bea75d4279a96627a4da49631dc3b81f486fcd2ead096fa96831 133 non-free/binary-armel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-armhf/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/binary-armhf/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-armhf/Packages.xz
4cddc7a3787e70376316186145565b1de2bbf50ade4f5183eeab85d2c7069b83 133 non-free/binary-armhf/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-i386/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/binary-i386/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-i386/Packages.xz
149101f480b7ab75f59882ea752607f27fac96be5b8696e7d467ac4fb6f459c3 132 non-free/binary-i386/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-mips64el/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/binary-mips64el/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-mips64el/Packages.xz
4eb4c5bc3f0b35263adef552c3e843e678c102af0bee91b90d8958516f8592c6 136 non-free/binary-mips64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-mipsel/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/binary-mipsel/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-mipsel/Packages.xz
a9c9896fa7a441c2554b9b369b8bb8f5fdf2c9098d60b5a49b5208314ec076a0 134 non-free/binary-mipsel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-ppc64el/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/binary-ppc64el/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-ppc64el/Packages.xz
a175762edf42deda74e5e1944cb613c84c769916b8186d71939b784de861edf6 135 non-free/binary-ppc64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-s390x/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/binary-s390x/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-s390x/Packages.xz
ecc1d55a4e9884e2ec3cebc0fa7e774e220c388406390e9b6c87e0884d627121 133 non-free/binary-s390x/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-all/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-all/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-all/Packages.xz
09692a805a856602cecf048c99e3371a98d391bbde03f6f3b1b348fd14988f57 131 non-free/debian-installer/binary-all/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-amd64/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-amd64/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-amd64/Packages.xz
62f830e8bbe3cdae15bd5eb6a49a7eab262e3b4f1ea05dd15334d150e44d2067 133 non-free/debian-installer/binary-amd64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-arm64/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-arm64/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-arm64/Packages.xz
d9bfd44bcf537cce90d40ea23f58b87c5b9a648bf72fdeaeb1d15cd797db1575 133 non-free/debian-installer/binary-arm64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-armel/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-armel/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-armel/Packages.xz
7bf07a333833bea75d4279a96627a4da49631dc3b81f486fcd2ead096fa96831 133 non-free/debian-installer/binary-armel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-armhf/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-armhf/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-armhf/Packages.xz
4cddc7a3787e70376316186145565b1de2bbf50ade4f5183eeab85d2c7069b83 133 non-free/debian-installer/binary-armhf/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-i386/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-i386/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-i386/Packages.xz
149101f480b7ab75f59882ea752607f27fac96be5b8696e7d467ac4fb6f459c3 132 non-free/debian-installer/binary-i386/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-mips64el/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-mips64el/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-mips64el/Packages.xz
4eb4c5bc3f0b35263adef552c3e843e678c102af0bee91b90d8958516f8592c6 136 non-free/debian-installer/binary-mips64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-mipsel/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-mipsel/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-mipsel/Packages.xz
a9c9896fa7a441c2554b9b369b8bb8f5fdf2c9098d60b5a49b5208314ec076a0 134 non-free/debian-installer/binary-mipsel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-ppc64el/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-ppc64el/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-ppc64el/Packages.xz
a175762edf42deda74e5e1944cb613c84c769916b8186d71939b784de861edf6 135 non-free/debian-installer/binary-ppc64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-s390x/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-s390x/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-s390x/Packages.xz
ecc1d55a4e9884e2ec3cebc0fa7e774e220c388406390e9b6c87e0884d627121 133 non-free/debian-installer/binary-s390x/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/i18n/Translation-en
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/i18n/Translation-en.xz
16dd4854d2bfc851746dec540a6f40c65db70dd5f8dcbeb2450182ce0fa3e306 134 non-free/source/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/source/Sources
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/source/Sources.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/source/Sources.xz
-----BEGIN PGP SIGNATURE-----
iQIzBAEBCAAdFiEEN5SD2LYBYLFVs3Ldqo6BtDMff1AFAmDbAjQACgkQqo6BtDMf
f1AeSA//efWHS9SsdL69FqFvfFA4TBI5HRcWRnRDiaVrq4c1FpfUjV+FwgCzFjql
y5MqjOfhI2bnn9T8DHLiK1NY3q6H6RVTpjzD0YdVZ7i8Eobp+9YOkjgQ+fhVqDpS
Xuka5kw9rYbkZgONIHNpsQwgnPupfG8O5OTHPDCwO49hPfa8g0Ar9kwYbixguVRX
qBYXV/EOyx6TXM5u69nqnbIzgdOhTHipJYVi9AiEyiO+lwAbzvogaSn574EWDb5y
9pruXxqHYFea4uGShJtambZMeC528CQALbkZpHzeFCdlBtapSH9Lxmye3ciqC9kT
7Ns09+G/C/3kTWVBfA7rVztJ5Bw4PWPZafe/YceenuK0EipsOa94PpmKeDkWzRL6
1obFDHWpDXqEJIomkq1+BkflWq8mQiI97v36NpIBXKew/f+jWw+ydoIZMESdWM2H
LGXF4fM3tCLGOYVqz6sAfUv7jfemJuDQq6ZNDVvfVIwwdA/p+oeTdu5NaYcOEGRQ
nXxS4MLWox6kc9UUWpE72NHHm5Anq62L/F7JVKbl7pw9tw0C/pvgF/6ZlnloOYZv
g3JBpZd7bt3UCDMU8MoEKs/ZDVk3x6Q/k9H/+S6B3M98UAjRQI0p2cgXQqvWuYdL
E0mXb4vKcS5wE/qyeFq7hSB1MP6q4x5kcKZe8333Odj4K4BDPCGJAjMEAQEIAB0W
IQRSN87u8hLz1Rx0q+ARJpWg5WKzKgUCYNsCNAAKCRARJpWg5WKzKp93EACBl90s
J9hX0OjJbMlIlDR+bBYaD9k6EjONtZUKGMVGWWQkpuQKlHZqEEjcQaU1kIpmyiEv
C0R44im2ULdb4jmuyMKmt3KdGMMCdD1yIaaLEoY/7GE9ZANojIWLsDdhgCADW4o5
WPruSxtpBKxKmyANw4EkbHUTyNtZ109cPdyX+isqElD47JNx+yypf1JOUdgW75xF
P96EKOFmQJahhAz2cCAjgBjy5awjGiz7AFtS13sBhbd7rn6CwTTdZ7JMxkh+UP/5
Fl93hmIRe3b1c12woB960BS2ic2dLW7nCIUId8LSl13xGc3y2qY7jd+XKVCbsuaZ
6R67dMGuMi2wmvu2aKbzuopVgSRJHpjZWiR5InOTesRczRw4UPrlfUKmuElxeGTi
BZxC1QtdB8ioRWmU7ArHO90v6zkxfXOhLCs5wOdkMB6kDB95CzmEjU985w3H5Hiz
GRAxThFpkcAgn96IFeIFvXk8xRLzPTyr3Xdm5QGiX6rZOYcM7BeH7+wyuboXsz4z
KCaTE53e1ANJSQOlxzozrHuPQ9c05lD4tos+gwBOm15y6Jwdf8+uCM7dwFhHjp50
65+QMfdcqkTGcRY3fcJsS3Yfu9gluQ916YBuUU03PNUDeaC6XPtbTVMLMsbQshWu
za+2eAEWLuyd9bvQ2VKilIJu7bozK8E5Y9UPng==
=Bno8
-----END PGP SIGNATURE-----

View File

@ -0,0 +1,422 @@
use std::path::PathBuf;
use anyhow::{bail, format_err, Error};
use proxmox_apt::config::APTConfig;
use proxmox_apt::repositories::{
check_repositories, get_current_release_codename, standard_repositories, APTRepositoryFile,
APTRepositoryHandle, APTRepositoryInfo, APTStandardRepository, DebianCodename,
};
#[test]
fn test_parse_write() -> Result<(), Error> {
test_parse_write_dir("sources.list.d")?;
test_parse_write_dir("sources.list.d.expected")?; // check if it's idempotent
Ok(())
}
fn test_parse_write_dir(read_dir: &str) -> Result<(), Error> {
let test_dir = std::env::current_dir()?.join("tests");
let read_dir = test_dir.join(read_dir);
let write_dir = test_dir.join("sources.list.d.actual");
let expected_dir = test_dir.join("sources.list.d.expected");
if write_dir.is_dir() {
std::fs::remove_dir_all(&write_dir)
.map_err(|err| format_err!("unable to remove dir {:?} - {}", write_dir, err))?;
}
std::fs::create_dir_all(&write_dir)
.map_err(|err| format_err!("unable to create dir {:?} - {}", write_dir, err))?;
let mut files = vec![];
let mut errors = vec![];
for entry in std::fs::read_dir(read_dir)? {
let path = entry?.path();
match APTRepositoryFile::new(&path)? {
Some(mut file) => match file.parse() {
Ok(()) => files.push(file),
Err(err) => errors.push(err),
},
None => bail!("unexpected None for '{:?}'", path),
}
}
assert!(errors.is_empty());
for file in files.iter_mut() {
let path = match &file.path {
Some(path) => path,
None => continue,
};
let path = PathBuf::from(path);
let new_path = write_dir.join(path.file_name().unwrap());
file.path = Some(new_path.into_os_string().into_string().unwrap());
file.digest = None;
file.write()?;
}
let mut expected_count = 0;
for entry in std::fs::read_dir(expected_dir)? {
expected_count += 1;
let expected_path = entry?.path();
let actual_path = write_dir.join(expected_path.file_name().unwrap());
let expected_contents = std::fs::read(&expected_path)
.map_err(|err| format_err!("unable to read {:?} - {}", expected_path, err))?;
let actual_contents = std::fs::read(&actual_path)
.map_err(|err| format_err!("unable to read {:?} - {}", actual_path, err))?;
assert_eq!(
expected_contents, actual_contents,
"Use\n\ndiff {:?} {:?}\n\nif you're not fluent in byte decimals",
expected_path, actual_path
);
}
let actual_count = std::fs::read_dir(write_dir)?.count();
assert_eq!(expected_count, actual_count);
Ok(())
}
#[test]
fn test_digest() -> Result<(), Error> {
let test_dir = std::env::current_dir()?.join("tests");
let read_dir = test_dir.join("sources.list.d");
let write_dir = test_dir.join("sources.list.d.digest");
if write_dir.is_dir() {
std::fs::remove_dir_all(&write_dir)
.map_err(|err| format_err!("unable to remove dir {:?} - {}", write_dir, err))?;
}
std::fs::create_dir_all(&write_dir)
.map_err(|err| format_err!("unable to create dir {:?} - {}", write_dir, err))?;
let path = read_dir.join("standard.list");
let mut file = APTRepositoryFile::new(&path)?.unwrap();
file.parse()?;
let new_path = write_dir.join(path.file_name().unwrap());
file.path = Some(new_path.clone().into_os_string().into_string().unwrap());
let old_digest = file.digest.unwrap();
// file does not exist yet...
assert!(file.read_with_digest().is_err());
assert!(file.write().is_err());
// ...but it should work if there's no digest
file.digest = None;
file.write()?;
// overwrite with old contents...
std::fs::copy(path, new_path)?;
// modify the repo
let mut repo = file.repositories.first_mut().unwrap();
repo.enabled = !repo.enabled;
// ...then it should work
file.digest = Some(old_digest);
file.write()?;
// expect a different digest, because the repo was modified
let (_, new_digest) = file.read_with_digest()?;
assert_ne!(old_digest, new_digest);
assert!(file.write().is_err());
Ok(())
}
#[test]
fn test_empty_write() -> Result<(), Error> {
let test_dir = std::env::current_dir()?.join("tests");
let read_dir = test_dir.join("sources.list.d");
let write_dir = test_dir.join("sources.list.d.remove");
if write_dir.is_dir() {
std::fs::remove_dir_all(&write_dir)
.map_err(|err| format_err!("unable to remove dir {:?} - {}", write_dir, err))?;
}
std::fs::create_dir_all(&write_dir)
.map_err(|err| format_err!("unable to create dir {:?} - {}", write_dir, err))?;
let path = read_dir.join("standard.list");
let mut file = APTRepositoryFile::new(&path)?.unwrap();
file.parse()?;
let new_path = write_dir.join(path.file_name().unwrap());
file.path = Some(new_path.into_os_string().into_string().unwrap());
file.digest = None;
file.write()?;
assert!(file.exists());
file.repositories.clear();
file.write()?;
assert!(!file.exists());
Ok(())
}
#[test]
fn test_check_repositories() -> Result<(), Error> {
let test_dir = std::env::current_dir()?.join("tests");
let read_dir = test_dir.join("sources.list.d");
proxmox_apt::config::init(APTConfig::new(
Some(&test_dir.into_os_string().into_string().unwrap()),
None,
));
let absolute_suite_list = read_dir.join("absolute_suite.list");
let mut file = APTRepositoryFile::new(&absolute_suite_list)?.unwrap();
file.parse()?;
let infos = check_repositories(&[file], DebianCodename::Bullseye);
assert!(infos.is_empty());
let pve_list = read_dir.join("pve.list");
let mut file = APTRepositoryFile::new(&pve_list)?.unwrap();
file.parse()?;
let path_string = pve_list.into_os_string().into_string().unwrap();
let origins = [
"Debian", "Debian", "Proxmox", "Proxmox", "Proxmox", "Debian",
];
let mut expected_infos = vec![];
for (n, origin) in origins.into_iter().enumerate() {
expected_infos.push(APTRepositoryInfo {
path: path_string.clone(),
index: n,
property: None,
kind: "origin".to_string(),
message: origin.to_string(),
});
}
expected_infos.sort();
let mut infos = check_repositories(&[file], DebianCodename::Bullseye);
infos.sort();
assert_eq!(infos, expected_infos);
let bad_sources = read_dir.join("bad.sources");
let mut file = APTRepositoryFile::new(&bad_sources)?.unwrap();
file.parse()?;
let path_string = bad_sources.into_os_string().into_string().unwrap();
let mut expected_infos = vec![
APTRepositoryInfo {
path: path_string.clone(),
index: 0,
property: Some("Suites".to_string()),
kind: "warning".to_string(),
message: "suite 'sid' should not be used in production!".to_string(),
},
APTRepositoryInfo {
path: path_string.clone(),
index: 1,
property: Some("Suites".to_string()),
kind: "warning".to_string(),
message: "old suite 'lenny' configured!".to_string(),
},
APTRepositoryInfo {
path: path_string.clone(),
index: 2,
property: Some("Suites".to_string()),
kind: "warning".to_string(),
message: "old suite 'stretch' configured!".to_string(),
},
APTRepositoryInfo {
path: path_string.clone(),
index: 3,
property: Some("Suites".to_string()),
kind: "warning".to_string(),
message: "use the name of the stable distribution instead of 'stable'!".to_string(),
},
APTRepositoryInfo {
path: path_string.clone(),
index: 4,
property: Some("Suites".to_string()),
kind: "ignore-pre-upgrade-warning".to_string(),
message: "suite 'bookworm' should not be used in production!".to_string(),
},
APTRepositoryInfo {
path: path_string.clone(),
index: 5,
property: Some("Suites".to_string()),
kind: "warning".to_string(),
message: "suite 'testing' should not be used in production!".to_string(),
},
];
for n in 0..=5 {
expected_infos.push(APTRepositoryInfo {
path: path_string.clone(),
index: n,
property: None,
kind: "origin".to_string(),
message: "Debian".to_string(),
});
}
expected_infos.sort();
let mut infos = check_repositories(&[file], DebianCodename::Bullseye);
infos.sort();
assert_eq!(infos, expected_infos);
let bad_security = read_dir.join("bad-security.list");
let mut file = APTRepositoryFile::new(&bad_security)?.unwrap();
file.parse()?;
let path_string = bad_security.into_os_string().into_string().unwrap();
let mut expected_infos = vec![];
for n in 0..=1 {
expected_infos.push(APTRepositoryInfo {
path: path_string.clone(),
index: n,
property: Some("Suites".to_string()),
kind: "warning".to_string(),
message: "expected suite 'bullseye-security'".to_string(),
});
}
for n in 0..=1 {
expected_infos.push(APTRepositoryInfo {
path: path_string.clone(),
index: n,
property: None,
kind: "origin".to_string(),
message: "Debian".to_string(),
});
}
expected_infos.sort();
let mut infos = check_repositories(&[file], DebianCodename::Bullseye);
infos.sort();
assert_eq!(infos, expected_infos);
Ok(())
}
#[test]
fn test_get_cached_origin() -> Result<(), Error> {
let test_dir = std::env::current_dir()?.join("tests");
let read_dir = test_dir.join("sources.list.d");
proxmox_apt::config::init(APTConfig::new(
Some(&test_dir.into_os_string().into_string().unwrap()),
None,
));
let pve_list = read_dir.join("pve.list");
let mut file = APTRepositoryFile::new(&pve_list)?.unwrap();
file.parse()?;
let origins = [
Some("Debian".to_string()),
Some("Debian".to_string()),
Some("Proxmox".to_string()),
None, // no cache file exists
None, // no cache file exists
Some("Debian".to_string()),
];
assert_eq!(file.repositories.len(), origins.len());
for (n, repo) in file.repositories.iter().enumerate() {
assert_eq!(repo.get_cached_origin()?, origins[n]);
}
Ok(())
}
#[test]
fn test_standard_repositories() -> Result<(), Error> {
let test_dir = std::env::current_dir()?.join("tests");
let read_dir = test_dir.join("sources.list.d");
let mut expected = vec![
APTStandardRepository::from(APTRepositoryHandle::Enterprise),
APTStandardRepository::from(APTRepositoryHandle::NoSubscription),
APTStandardRepository::from(APTRepositoryHandle::Test),
APTStandardRepository::from(APTRepositoryHandle::CephQuincy),
APTStandardRepository::from(APTRepositoryHandle::CephQuincyTest),
APTStandardRepository::from(APTRepositoryHandle::CephPacific),
APTStandardRepository::from(APTRepositoryHandle::CephPacificTest),
APTStandardRepository::from(APTRepositoryHandle::CephOctopus),
APTStandardRepository::from(APTRepositoryHandle::CephOctopusTest),
];
let absolute_suite_list = read_dir.join("absolute_suite.list");
let mut file = APTRepositoryFile::new(&absolute_suite_list)?.unwrap();
file.parse()?;
let std_repos = standard_repositories(&[file], "pve", DebianCodename::Bullseye);
assert_eq!(std_repos, expected);
let pve_list = read_dir.join("pve.list");
let mut file = APTRepositoryFile::new(&pve_list)?.unwrap();
file.parse()?;
let file_vec = vec![file];
let std_repos = standard_repositories(&file_vec, "pbs", DebianCodename::Bullseye);
assert_eq!(&std_repos, &expected[0..=2]);
expected[0].status = Some(false);
expected[1].status = Some(true);
let std_repos = standard_repositories(&file_vec, "pve", DebianCodename::Bullseye);
assert_eq!(std_repos, expected);
let pve_alt_list = read_dir.join("pve-alt.list");
let mut file = APTRepositoryFile::new(&pve_alt_list)?.unwrap();
file.parse()?;
let file_vec = vec![file];
expected[0].status = Some(true);
expected[1].status = Some(true);
expected[2].status = Some(false);
let std_repos = standard_repositories(&file_vec, "pve", DebianCodename::Bullseye);
assert_eq!(std_repos, expected);
Ok(())
}
#[test]
fn test_get_current_release_codename() -> Result<(), Error> {
let codename = get_current_release_codename()?;
assert!(codename == DebianCodename::Bullseye);
Ok(())
}

View File

@ -0,0 +1,5 @@
# From Debian Administrator's Handbook
deb http://packages.falcot.com/ updates/
deb http://user.name@packages.falcot.com:80/ internal/

View File

@ -0,0 +1,5 @@
# From Debian Administrator's Handbook
Types: deb
URIs: http://packages.falcot.com/
Suites: updates/ internal/

View File

@ -0,0 +1,4 @@
deb http://security.debian.org/debian-security/ bullseye/updates main contrib
deb https://security.debian.org bullseye/updates main contrib

View File

@ -0,0 +1,30 @@
Types: deb
URIs: http://ftp.at.debian.org/debian
Suites: sid
Components: main contrib
Types: deb
URIs: http://ftp.at.debian.org/debian
Suites: lenny-backports
Components: contrib
Types: deb
URIs: http://security.debian.org:80
Suites: stretch/updates
Components: main contrib
Types: deb
URIs: http://ftp.at.debian.org:80/debian
Suites: stable
Components: main
Types: deb
URIs: http://ftp.at.debian.org/debian
Suites: bookworm
Components: main
Types: deb
URIs: http://ftp.at.debian.org/debian
Suites: testing
Components: main

View File

@ -0,0 +1,16 @@
# comment in here
Types: deb deb-src
URIs: http://ftp.at.debian.org/debian
Suites: bullseye-updates
Components: main contrib
languages: it de fr
Enabled: false
languages-Add: ja
languages-Remove: de
# comment in here
Types: deb deb-src
URIs: http://ftp.at.debian.org/debian
Suites: bullseye
Components: main contrib

View File

@ -0,0 +1,10 @@
# deb [ trusted=yes ] cdrom:[Proxmox VE 5.1]/ stretch pve
# deb [ trusted=yes ] cdrom:[Proxmox VE 5.1]/proxmox/packages/ /
deb [ trusted=yes ] cdrom:[Proxmox VE 7.0 BETA]/ bullseye pve
deb cdrom:[Proxmox VE 7.0 BETA]/proxmox/packages/ /
deb [ trusted=yes ] cdrom:[Debian GNU/Linux 10.6.0 _Buster_ - Official amd64 NETINST 20200926-10:16]/ buster main

View File

@ -0,0 +1,4 @@
deb [ trusted=yes ] "file:///some/spacey/mount point/" bullseye pve
deb [ lang=it ] "file:///some/spacey/mount point/proxmox/packages/" /

View File

@ -0,0 +1,10 @@
# comment in here
Types: deb deb-src
URIs: http://ftp.at.debian.org/debian
Suites: bullseye bullseye-updates
Components: main contrib
Languages: it de fr
Enabled: false
Languages-Add: ja
Languages-Remove: de

View File

@ -0,0 +1,6 @@
# comment
deb [ lang=it,de arch=amd64 ] http://ftp.at.debian.org/debian bullseye main contrib
# non-free :(
deb [ lang=it,de arch=amd64 lang+=fr lang-=de ] http://ftp.at.debian.org/debian bullseye non-free

View File

@ -0,0 +1,2 @@
deb https://enterprise.proxmox.com/debian/pbs bullseye pbs-enterprise

View File

@ -0,0 +1,8 @@
deb https://enterprise.proxmox.com/debian bullseye pve-enterprise
deb http://download.proxmox.com/debian/ bullseye pve-no-subscription
# deb http://download.proxmox.com/debian bullseye pvetest
deb-src http://download.proxmox.com/debian bullseye pvetest

View File

@ -0,0 +1,15 @@
deb http://ftp.debian.org/debian bullseye main contrib
deb http://ftp.debian.org/debian bullseye-updates main contrib
# PVE pve-no-subscription repository provided by proxmox.com,
# NOT recommended for production use
deb http://download.proxmox.com/debian/pve bullseye pve-no-subscription
# deb https://enterprise.proxmox.com/debian/pve bullseye pve-enterprise
deb-src https://enterprise.proxmox.com/debian/pve buster pve-enterprise
# security updates
deb http://security.debian.org/debian-security bullseye-security main contrib

View File

@ -0,0 +1,7 @@
deb http://ftp.at.debian.org/debian bullseye main contrib
deb http://ftp.at.debian.org/debian bullseye-updates main contrib
# security updates
deb http://security.debian.org bullseye-security main contrib

View File

@ -0,0 +1,11 @@
Types: deb
URIs: http://ftp.at.debian.org/debian
Suites: bullseye bullseye-updates
Components: main contrib
# security updates
Types: deb
URIs: http://security.debian.org
Suites: bullseye-security
Components: main contrib

View File

@ -0,0 +1,4 @@
# From Debian Administrator's Handbook
deb http://packages.falcot.com/ updates/
deb http://user.name@packages.falcot.com:80/ internal/

View File

@ -0,0 +1,5 @@
# From Debian Administrator's Handbook
Types: deb
URIs: http://packages.falcot.com/
Suites: updates/ internal/

View File

@ -0,0 +1,4 @@
deb http://security.debian.org/debian-security/ bullseye/updates main contrib
deb https://security.debian.org bullseye/updates main contrib

View File

@ -0,0 +1,29 @@
Types: deb
URIs: http://ftp.at.debian.org/debian
Suites: sid
Components: main contrib
Types: deb
URIs: http://ftp.at.debian.org/debian
Suites: lenny-backports
Components: contrib
Types: deb
URIs: http://security.debian.org:80
Suites: stretch/updates
Components: main contrib
Suites: stable
URIs: http://ftp.at.debian.org:80/debian
Components: main
Types: deb
Suites: bookworm
URIs: http://ftp.at.debian.org/debian
Components: main
Types: deb
Suites: testing
URIs: http://ftp.at.debian.org/debian
Components: main
Types: deb

View File

@ -0,0 +1,17 @@
tYpeS: deb deb-src
uRis: http://ftp.at.debian.org/debian
suiTes: bullseye-updates
# comment in here
CompOnentS: main contrib
languages: it
de
fr
Enabled: off
languages-Add: ja
languages-Remove: de
types: deb deb-src
Uris: http://ftp.at.debian.org/debian
suites: bullseye
# comment in here
components: main contrib

View File

@ -0,0 +1,7 @@
#deb [trusted=yes] cdrom:[Proxmox VE 5.1]/ stretch pve
#deb [trusted=yes] cdrom:[Proxmox VE 5.1]/proxmox/packages/ /
deb [trusted=yes] cdrom:[Proxmox VE 7.0 BETA]/ bullseye pve
deb cdrom:[Proxmox VE 7.0 BETA]/proxmox/packages/ /
deb [ "trusted=yes" ] cdrom:[Debian GNU/Linux 10.6.0 _Buster_ - Official amd64 NETINST 20200926-10:16]/ buster main

View File

@ -0,0 +1,2 @@
deb [trusted=yes] "file:///some/spacey/mount point/" bullseye pve
deb [lang="it"] file:///some/spacey/"mount point"/proxmox/packages/ /

View File

@ -0,0 +1,11 @@
Types: deb deb-src
URIs: http://ftp.at.debian.org/debian
Suites: bullseye bullseye-updates
# comment in here
Components: main contrib
Languages: it
de
fr
Enabled: off
Languages-Add: ja
Languages-Remove: de

View File

@ -0,0 +1,3 @@
deb [ lang=it,de arch=amd64 ] http://ftp.at.debian.org/debian bullseye main contrib # comment
deb [ lang=it,de arch=amd64 lang+=fr lang-=de ] http://ftp.at.debian.org/debian bullseye non-free # non-free :(

View File

@ -0,0 +1 @@
deb https://enterprise.proxmox.com/debian/pbs bullseye pbs-enterprise

View File

@ -0,0 +1,6 @@
deb https://enterprise.proxmox.com/debian bullseye pve-enterprise
deb http://download.proxmox.com/debian/ bullseye pve-no-subscription
# deb http://download.proxmox.com/debian bullseye pvetest
deb-src http://download.proxmox.com/debian bullseye pvetest

View File

@ -0,0 +1,12 @@
deb http://ftp.debian.org/debian bullseye main contrib
deb http://ftp.debian.org/debian bullseye-updates main contrib
# PVE pve-no-subscription repository provided by proxmox.com,
# NOT recommended for production use
deb http://download.proxmox.com/debian/pve bullseye pve-no-subscription
# deb https://enterprise.proxmox.com/debian/pve bullseye pve-enterprise
deb-src https://enterprise.proxmox.com/debian/pve buster pve-enterprise
# security updates
deb http://security.debian.org/debian-security bullseye-security main contrib

View File

@ -0,0 +1,6 @@
deb http://ftp.at.debian.org/debian bullseye main contrib
deb http://ftp.at.debian.org/debian bullseye-updates main contrib
# security updates
deb http://security.debian.org bullseye-security main contrib

View File

@ -0,0 +1,10 @@
Types: deb
URIs: http://ftp.at.debian.org/debian
Suites: bullseye bullseye-updates
Components: main contrib
# security updates
Types: deb
URIs: http://security.debian.org
Suites: bullseye-security
Components: main contrib

27
proxmox-openid/Cargo.toml Normal file
View File

@ -0,0 +1,27 @@
[package]
name = "proxmox-openid"
version = "0.9.9"
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
exclude = [
"build",
"debian",
]
[dependencies]
anyhow.workspace = true
http.workspace = true
nix.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
thiserror = "1"
native-tls.workspace = true
url.workspace = true
openidconnect = { version = "2.4", default-features = false, features = ["accept-rfc3339-timestamps"] }
ureq = { version = "2.4", default-features = false, features = ["native-tls", "gzip"] }
proxmox-time.workspace = true
proxmox-sys = { workspace = true, features = ["timer"] }

View File

@ -0,0 +1,141 @@
rust-proxmox-openid (0.9.9-1) stable; urgency=medium
* update openidconnect to 2.4
-- Proxmox Support Team <support@proxmox.com> Wed, 11 Jan 2023 18:41:25 +0100
rust-proxmox-openid (0.9.8-1) stable; urgency=medium
* update nix to 0.26
-- Proxmox Support Team <support@proxmox.com> Thu, 05 Jan 2023 12:25:10 +0100
rust-proxmox-openid (0.9.7-1) stable; urgency=medium
* bump proxmox-sys to 0.4
-- Proxmox Support Team <support@proxmox.com> Thu, 28 Jul 2022 13:40:44 +0200
rust-proxmox-openid (0.9.6-1) stable; urgency=medium
* rebuild with nix 0.24 and proxmox-sys 0.3
-- Proxmox Support Team <support@proxmox.com> Thu, 2 Jun 2022 12:38:28 +0200
rust-proxmox-openid (0.9.5-1) stable; urgency=medium
* avoid chunked transfer-encoding when submitting to the provider's token
endpoint, as some providers like Microsoft's Azure are quite inflexible
and cannot cope with such basic HTTP requests.
-- Proxmox Support Team <support@proxmox.com> Fri, 01 Apr 2022 15:56:07 +0200
rust-proxmox-openid (0.9.4-1) stable; urgency=medium
* re-add HTTP proxy support via the ALL_PROXY environment variable. This got
lost with switching the HTTP client from curl to ureq.
-- Proxmox Support Team <support@proxmox.com> Tue, 22 Mar 2022 11:31:08 +0100
rust-proxmox-openid (0.9.3-1) stable; urgency=medium
* use much simpler ureq (with native-tls) HTTP client instead of curl
* enable "accept-rfc3339-timestamps" feature to fix support for some OIDC
providers like `auth0`
-- Proxmox Support Team <support@proxmox.com> Tue, 01 Feb 2022 09:08:31 +0100
rust-proxmox-openid (0.9.2-1) stable; urgency=medium
* depend on proxmox-sys 0.2
-- Proxmox Support Team <support@proxmox.com> Tue, 23 Nov 2021 12:35:41 +0100
rust-proxmox-openid (0.9.1-1) unstable; urgency=medium
* rebuild with openidconnect 0.2.1
-- Proxmox Support Team <support@proxmox.com> Thu, 18 Nov 2021 12:54:24 +0100
rust-proxmox-openid (0.9.0-1) unstable; urgency=medium
* allow to configure used scopes
* allow to configure prompt behaviour
* allow to configure acr values
* new helper verify_authorization_code_simple()
* also return data from UserInfo endpoint
-- Proxmox Support Team <support@proxmox.com> Thu, 18 Nov 2021 09:36:29 +0100
rust-proxmox-openid (0.8.1-1) unstable; urgency=medium
* add fsync parameter to replace_file
* Depend on proxmox 0.15.0
-- Proxmox Support Team <support@proxmox.com> Thu, 21 Oct 2021 07:14:52 +0200
rust-proxmox-openid (0.8.0-1) unstable; urgency=medium
* update to proxmox crate split
-- Proxmox Support Team <support@proxmox.com> Fri, 08 Oct 2021 12:19:55 +0200
rust-proxmox-openid (0.7.0-1) unstable; urgency=medium
* bump proxmox to 0.13.0
-- Proxmox Support Team <support@proxmox.com> Tue, 24 Aug 2021 16:06:55 +0200
rust-proxmox-openid (0.6.1-1) unstable; urgency=medium
* depend on proxmox 0.12.0
-- Proxmox Support Team <support@proxmox.com> Tue, 20 Jul 2021 13:19:23 +0200
rust-proxmox-openid (0.6.0-2) unstable; urgency=medium
* remove debug output
-- Proxmox Support Team <support@proxmox.com> Wed, 30 Jun 2021 08:43:06 +0200
rust-proxmox-openid (0.6.0-1) unstable; urgency=medium
* use one lock file per realm
-- Proxmox Support Team <support@proxmox.com> Fri, 25 Jun 2021 11:09:08 +0200
rust-proxmox-openid (0.5.0-1) unstable; urgency=medium
* avoid unused features "sortable-macro" and "api-macro"
-- Proxmox Support Team <support@proxmox.com> Wed, 23 Jun 2021 11:29:05 +0200
rust-proxmox-openid (0.4.0-1) unstable; urgency=medium
* set "default-features = false" for proxmox crate
-- Proxmox Support Team <support@proxmox.com> Wed, 23 Jun 2021 11:17:22 +0200
rust-proxmox-openid (0.3.0-1) unstable; urgency=medium
* return authorize_url() as string
-- Proxmox Support Team <support@proxmox.com> Tue, 22 Jun 2021 09:23:33 +0200
rust-proxmox-openid (0.2.0-1) devel; urgency=medium
* implement Deserialize/Serialize for OpenIdConfig
-- Proxmox Support Team <support@proxmox.com> Mon, 21 Jun 2021 13:37:24 +0200
rust-proxmox-openid (0.1.0-1) devel; urgency=medium
* initial release
-- Proxmox Support Team <support@proxmox.com> Fri, 18 Jun 2021 16:05:49 +0200

View File

@ -0,0 +1,61 @@
Source: rust-proxmox-openid
Section: rust
Priority: optional
Build-Depends: debhelper (>= 12),
dh-cargo (>= 25),
cargo:native <!nocheck>,
rustc:native <!nocheck>,
libstd-rust-dev <!nocheck>,
librust-anyhow-1+default-dev <!nocheck>,
librust-http-0.2+default-dev <!nocheck>,
librust-native-tls-0.2+default-dev <!nocheck>,
librust-nix-0.26+default-dev (>= 0.26.1-~~) <!nocheck>,
librust-openidconnect-2+accept-rfc3339-timestamps-dev (>= 2.4-~~) <!nocheck>,
librust-proxmox-sys-0.4+default-dev (>= 0.4.2-~~) <!nocheck>,
librust-proxmox-sys-0.4+timer-dev (>= 0.4.2-~~) <!nocheck>,
librust-proxmox-time-1+default-dev (>= 1.1.4-~~) <!nocheck>,
librust-serde-1+default-dev <!nocheck>,
librust-serde-1+derive-dev <!nocheck>,
librust-serde-json-1+default-dev <!nocheck>,
librust-thiserror-1+default-dev <!nocheck>,
librust-ureq-2+gzip-dev (>= 2.4-~~) <!nocheck>,
librust-ureq-2+native-tls-dev (>= 2.4-~~) <!nocheck>,
librust-url-2+default-dev (>= 2.2-~~) <!nocheck>
Maintainer: Proxmox Support Team <support@proxmox.com>
Standards-Version: 4.6.1
Vcs-Git:
Vcs-Browser:
X-Cargo-Crate: proxmox-openid
Rules-Requires-Root: no
Package: librust-proxmox-openid-dev
Architecture: any
Multi-Arch: same
Depends:
${misc:Depends},
librust-anyhow-1+default-dev,
librust-http-0.2+default-dev,
librust-native-tls-0.2+default-dev,
librust-nix-0.26+default-dev (>= 0.26.1-~~),
librust-openidconnect-2+accept-rfc3339-timestamps-dev (>= 2.4-~~),
librust-proxmox-sys-0.4+default-dev (>= 0.4.2-~~),
librust-proxmox-sys-0.4+timer-dev (>= 0.4.2-~~),
librust-proxmox-time-1+default-dev (>= 1.1.4-~~),
librust-serde-1+default-dev,
librust-serde-1+derive-dev,
librust-serde-json-1+default-dev,
librust-thiserror-1+default-dev,
librust-ureq-2+gzip-dev (>= 2.4-~~),
librust-ureq-2+native-tls-dev (>= 2.4-~~),
librust-url-2+default-dev (>= 2.2-~~)
Provides:
librust-proxmox-openid+default-dev (= ${binary:Version}),
librust-proxmox-openid-0-dev (= ${binary:Version}),
librust-proxmox-openid-0+default-dev (= ${binary:Version}),
librust-proxmox-openid-0.9-dev (= ${binary:Version}),
librust-proxmox-openid-0.9+default-dev (= ${binary:Version}),
librust-proxmox-openid-0.9.9-dev (= ${binary:Version}),
librust-proxmox-openid-0.9.9+default-dev (= ${binary:Version})
Description: Rust crate "proxmox-openid" - Rust source code
This package contains the source for the Rust proxmox-openid crate, packaged by
debcargo for use with cargo and dh-cargo.

View File

@ -0,0 +1,18 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Files:
*
Copyright: 2019 - 2023 Proxmox Server Solutions GmbH <support@proxmox.com>
License: AGPL-3.0-or-later
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option) any
later version.
.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
details.
.
You should have received a copy of the GNU Affero General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>.

View File

@ -0,0 +1,8 @@
overlay = "."
crate_src_path = ".."
maintainer = "Proxmox Support Team <support@proxmox.com>"
[source]
# TODO: update once public
vcs_git = ""
vcs_browser = ""

View File

@ -0,0 +1,116 @@
use std::path::{Path, PathBuf};
use anyhow::{bail, Error};
use serde_json::{json, Value};
use proxmox_sys::fs::{
replace_file,
open_file_locked,
file_get_json,
CreateOptions,
};
use proxmox_time::epoch_i64;
use super::{PublicAuthState, PrivateAuthState};
fn load_auth_state_locked(
state_dir: &Path,
realm: &str,
default: Option<Value>,
) -> Result<(PathBuf, std::fs::File, Vec<Value>), Error> {
let mut lock_path = state_dir.to_owned();
lock_path.push(format!("proxmox-openid-auth-state-{}.lck", realm));
let lock = open_file_locked(
lock_path,
std::time::Duration::new(10, 0),
true,
CreateOptions::new()
)?;
let mut path = state_dir.to_owned();
path.push(format!("proxmox-openid-auth-state-{}", realm));
let now = epoch_i64();
let old_data = file_get_json(&path, default)?;
let mut data: Vec<Value> = Vec::new();
let timeout = 10*60; // 10 minutes
for v in old_data.as_array().unwrap() {
let ctime = v["ctime"].as_i64().unwrap_or(0);
if (ctime + timeout) < now {
continue;
}
data.push(v.clone());
}
Ok((path, lock, data))
}
fn replace_auth_state(
path: &Path,
data: &Vec<Value>,
) -> Result<(), Error> {
let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600);
let options = CreateOptions::new().perm(mode);
let raw = serde_json::to_string_pretty(data)?;
replace_file(path, raw.as_bytes(), options, false)?;
Ok(())
}
pub fn verify_public_auth_state(
state_dir: &Path,
state: &str,
) -> Result<(String, PrivateAuthState), Error> {
let public_auth_state: PublicAuthState = serde_json::from_str(state)?;
let (path, _lock, old_data) = load_auth_state_locked(state_dir, &public_auth_state.realm, None)?;
let mut data: Vec<Value> = Vec::new();
let mut entry: Option<PrivateAuthState> = None;
let find_csrf_token = public_auth_state.csrf_token.secret();
for v in old_data {
if v["csrf_token"].as_str() == Some(find_csrf_token) {
entry = Some(serde_json::from_value(v)?);
} else {
data.push(v);
}
}
let entry = match entry {
None => bail!("no openid auth state found (possible timeout)"),
Some(entry) => entry,
};
replace_auth_state(&path, &data)?;
Ok((public_auth_state.realm, entry))
}
pub fn store_auth_state(
state_dir: &Path,
realm: &str,
auth_state: &PrivateAuthState,
) -> Result<(), Error> {
let (path, _lock, mut data) = load_auth_state_locked(state_dir, realm, Some(json!([])))?;
if data.len() > 100 {
bail!("too many pending openid auth request for realm {}", realm);
}
data.push(serde_json::to_value(&auth_state)?);
replace_auth_state(&path, &data)?;
Ok(())
}

View File

@ -0,0 +1,100 @@
use std::env;
use std::sync::Arc;
use http::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
use http::method::Method;
use http::status::StatusCode;
use openidconnect::{HttpRequest, HttpResponse};
// Copied from OAuth2 create, because we want to use ureq with
// native-tls. But current OAuth2 crate pulls in rustls, so we cannot
// use their 'ureq' feature.
///
/// Error type returned by failed ureq HTTP requests.
///
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// Non-ureq HTTP error.
#[error("HTTP error - {0}")]
Http(#[from] http::Error),
/// IO error
#[error("IO error - {0}")]
IO(#[from] std::io::Error),
/// Error returned by ureq crate.
// boxed due to https://github.com/algesten/ureq/issues/296
#[error("ureq request failed - {0}")]
Ureq(#[from] Box<ureq::Error>),
#[error("TLS error - {0}")]
Tls(#[from] native_tls::Error),
/// Other error.
#[error("Other error: {0}")]
Other(String),
}
fn ureq_agent() -> Result<ureq::Agent, Error> {
let mut agent =
ureq::AgentBuilder::new().tls_connector(Arc::new(native_tls::TlsConnector::new()?));
if let Ok(val) = env::var("all_proxy").or_else(|_| env::var("ALL_PROXY")) {
let proxy = ureq::Proxy::new(val).map_err(Box::new)?;
agent = agent.proxy(proxy);
}
Ok(agent.build())
}
///
/// Synchronous HTTP client for ureq.
///
pub fn http_client(request: HttpRequest) -> Result<HttpResponse, Error> {
let agent = ureq_agent()?;
let mut req = if let Method::POST = request.method {
agent.post(&request.url.to_string())
} else {
agent.get(&request.url.to_string())
};
for (name, value) in request.headers {
if let Some(name) = name {
req = req.set(
&name.to_string(),
value.to_str().map_err(|_| {
Error::Other(format!(
"invalid {} header value {:?}",
name,
value.as_bytes()
))
})?,
);
}
}
let response = if let Method::POST = request.method {
// send_bytes makes sure that Content-Length is set. This is important, because some
// endpoints don't accept `Transfer-Encoding: chunked`, which would otherwise be set.
// see https://docs.rs/ureq/2.4.0/ureq/index.html#content-length-and-transfer-encoding
req.send_bytes(request.body.as_slice())
} else {
req.call()
}
.map_err(Box::new)?;
let status_code =
StatusCode::from_u16(response.status()).map_err(|err| Error::Http(err.into()))?;
let content_type =
HeaderValue::from_str(response.content_type()).map_err(|err| Error::Http(err.into()))?;
Ok(HttpResponse {
status_code,
headers: vec![(CONTENT_TYPE, content_type)]
.into_iter()
.collect::<HeaderMap>(),
body: response.into_string()?.as_bytes().into(),
})
}

253
proxmox-openid/src/lib.rs Normal file
View File

@ -0,0 +1,253 @@
use std::path::Path;
use anyhow::{format_err, Error};
use serde::{Deserialize, Serialize};
use serde_json::Value;
mod http_client;
pub use http_client::http_client;
mod auth_state;
pub use auth_state::*;
use openidconnect::{
//curl::http_client,
core::{
CoreProviderMetadata,
CoreClient,
CoreIdTokenClaims,
CoreIdTokenVerifier,
CoreAuthenticationFlow,
CoreAuthDisplay,
CoreAuthPrompt,
CoreGenderClaim,
},
PkceCodeChallenge,
PkceCodeVerifier,
AuthorizationCode,
ClientId,
ClientSecret,
CsrfToken,
IssuerUrl,
Nonce,
OAuth2TokenResponse,
RedirectUrl,
Scope,
UserInfoClaims,
AdditionalClaims,
AuthenticationContextClass,
};
/// Stores Additional Claims into a serde_json::Value;
#[derive(Debug, Deserialize, Serialize)]
pub struct GenericClaims(Value);
impl AdditionalClaims for GenericClaims {}
pub type GenericUserInfoClaims = UserInfoClaims<GenericClaims, CoreGenderClaim>;
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct OpenIdConfig {
pub issuer_url: String,
pub client_id: String,
#[serde(skip_serializing_if="Option::is_none")]
pub client_key: Option<String>,
#[serde(skip_serializing_if="Option::is_none")]
pub scopes: Option<Vec<String>>,
#[serde(skip_serializing_if="Option::is_none")]
pub prompt: Option<String>,
#[serde(skip_serializing_if="Option::is_none")]
pub acr_values: Option<Vec<String>>,
}
pub struct OpenIdAuthenticator {
client: CoreClient,
config: OpenIdConfig,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct PublicAuthState {
pub csrf_token: CsrfToken,
pub realm: String,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct PrivateAuthState {
pub csrf_token: CsrfToken,
pub nonce: Nonce,
pub pkce_verifier: PkceCodeVerifier,
pub ctime: i64,
}
impl PrivateAuthState {
pub fn new() -> Self {
let nonce = Nonce::new_random();
let csrf_token = CsrfToken::new_random();
let (_pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
PrivateAuthState {
csrf_token,
nonce,
pkce_verifier,
ctime: proxmox_time::epoch_i64(),
}
}
pub fn pkce_verifier(&self) -> PkceCodeVerifier {
// Note: PkceCodeVerifier does not impl. clone()
PkceCodeVerifier::new(self.pkce_verifier.secret().to_string())
}
pub fn pkce_challenge(&self) -> PkceCodeChallenge {
PkceCodeChallenge::from_code_verifier_sha256(&self.pkce_verifier)
}
pub fn public_state_string(&self, realm: String) -> Result<String, Error> {
let pub_state = PublicAuthState {
csrf_token: self.csrf_token.clone(),
realm,
};
Ok(serde_json::to_string(&pub_state)?)
}
}
impl OpenIdAuthenticator {
pub fn discover(config: &OpenIdConfig, redirect_url: &str) -> Result<Self, Error> {
let client_id = ClientId::new(config.client_id.clone());
let client_key = config.client_key.clone().map(|key| ClientSecret::new(key));
let issuer_url = IssuerUrl::new(config.issuer_url.clone())?;
let provider_metadata = CoreProviderMetadata::discover(&issuer_url, http_client)?;
let client = CoreClient::from_provider_metadata(
provider_metadata,
client_id,
client_key,
).set_redirect_uri(RedirectUrl::new(String::from(redirect_url))?);
Ok(Self {
client,
config: config.clone(),
})
}
pub fn authorize_url(&self, state_dir: &str, realm: &str) -> Result<String, Error> {
let private_auth_state = PrivateAuthState::new();
let public_auth_state = private_auth_state.public_state_string(realm.to_string())?;
let nonce = private_auth_state.nonce.clone();
store_auth_state(Path::new(state_dir), realm, &private_auth_state)?;
// Generate the authorization URL to which we'll redirect the user.
let mut request = self.client
.authorize_url(
CoreAuthenticationFlow::AuthorizationCode,
|| CsrfToken::new(public_auth_state),
|| nonce,
)
.set_pkce_challenge(private_auth_state.pkce_challenge());
request = request.set_display(CoreAuthDisplay::Page);
match self.config.prompt.as_deref() {
None => { /* nothing */ },
Some("none") => {
request = request.add_prompt(CoreAuthPrompt::None);
}
Some("login") => {
request = request.add_prompt(CoreAuthPrompt::Login);
}
Some("consent") => {
request = request.add_prompt(CoreAuthPrompt::Consent);
}
Some("select_account") => {
request = request.add_prompt(CoreAuthPrompt::SelectAccount);
}
Some(extension) => {
request = request.add_prompt(CoreAuthPrompt::Extension(extension.into()));
}
}
if let Some(ref scopes) = self.config.scopes {
for scope in scopes.clone() {
request = request.add_scope(Scope::new(scope));
}
}
if let Some(ref acr_values) = self.config.acr_values {
for acr in acr_values.clone() {
request = request.add_auth_context_value(AuthenticationContextClass::new(acr));
}
}
let (authorize_url, _csrf_state, _nonce) = request.url();
Ok(authorize_url.to_string())
}
pub fn verify_public_auth_state(
state_dir: &str,
state: &str,
) -> Result<(String, PrivateAuthState), Error> {
verify_public_auth_state(Path::new(state_dir), state)
}
pub fn verify_authorization_code(
&self,
code: &str,
private_auth_state: &PrivateAuthState,
) -> Result<(CoreIdTokenClaims, GenericUserInfoClaims), Error> {
let code = AuthorizationCode::new(code.to_string());
// Exchange the code with a token.
let token_response = self.client
.exchange_code(code)
.set_pkce_verifier(private_auth_state.pkce_verifier())
.request(http_client)
.map_err(|err| format_err!("Failed to contact token endpoint: {}", err))?;
let id_token_verifier: CoreIdTokenVerifier = self.client.id_token_verifier();
let id_token_claims: &CoreIdTokenClaims = token_response
.extra_fields()
.id_token()
.expect("Server did not return an ID token")
.claims(&id_token_verifier, &private_auth_state.nonce)
.map_err(|err| format_err!("Failed to verify ID token: {}", err))?;
let userinfo_claims: GenericUserInfoClaims = self.client
.user_info(token_response.access_token().to_owned(), None)?
.request(http_client)
.map_err(|err| format_err!("Failed to contact userinfo endpoint: {}", err))?;
Ok((id_token_claims.clone(), userinfo_claims))
}
/// Like verify_authorization_code(), but returns claims as serde_json::Value
pub fn verify_authorization_code_simple(
&self,
code: &str,
private_auth_state: &PrivateAuthState,
) -> Result<Value, Error> {
let (id_token_claims, userinfo_claims) = self.verify_authorization_code(&code, &private_auth_state)?;
let mut data = serde_json::to_value(id_token_claims)?;
let data2 = serde_json::to_value(userinfo_claims)?;
if let Some(map) = data2.as_object() {
for (key, value) in map {
if data[key] != Value::Null {
continue; // already set
}
data[key] = value.clone();
}
}
Ok(data)
}
}