Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
286c55d5b4 | ||
|
6ec35bdbbd | ||
|
82417de8a8 | ||
|
cc553060e0 |
@ -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
23
proxmox-apt/Cargo.toml
Normal 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" ] }
|
121
proxmox-apt/debian/changelog
Normal file
121
proxmox-apt/debian/changelog
Normal 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
|
52
proxmox-apt/debian/control
Normal file
52
proxmox-apt/debian/control
Normal 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.
|
18
proxmox-apt/debian/copyright
Normal file
18
proxmox-apt/debian/copyright
Normal 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/>.
|
7
proxmox-apt/debian/debcargo.toml
Normal file
7
proxmox-apt/debian/debcargo.toml
Normal 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
35
proxmox-apt/src/config.rs
Normal 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)
|
||||
}
|
75
proxmox-apt/src/deb822/mod.rs
Normal file
75
proxmox-apt/src/deb822/mod.rs
Normal 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(())
|
||||
}
|
||||
}
|
171
proxmox-apt/src/deb822/packages_file.rs
Normal file
171
proxmox-apt/src/deb822/packages_file.rs
Normal 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);
|
||||
}
|
618
proxmox-apt/src/deb822/release_file.rs
Normal file
618
proxmox-apt/src/deb822/release_file.rs
Normal 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);
|
||||
}
|
257
proxmox-apt/src/deb822/sources_file.rs
Normal file
257
proxmox-apt/src/deb822/sources_file.rs
Normal 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
3
proxmox-apt/src/lib.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod config;
|
||||
pub mod deb822;
|
||||
pub mod repositories;
|
469
proxmox-apt/src/repositories/file.rs
Normal file
469
proxmox-apt/src/repositories/file.rs
Normal 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, ""),
|
||||
}
|
||||
}
|
250
proxmox-apt/src/repositories/file/list_parser.rs
Normal file
250
proxmox-apt/src/repositories/file/list_parser.rs
Normal 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)
|
||||
}
|
||||
}
|
203
proxmox-apt/src/repositories/file/sources_parser.rs
Normal file
203
proxmox-apt/src/repositories/file/sources_parser.rs
Normal 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)
|
||||
}
|
||||
}
|
191
proxmox-apt/src/repositories/mod.rs
Normal file
191
proxmox-apt/src/repositories/mod.rs
Normal 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))
|
||||
}
|
100
proxmox-apt/src/repositories/release.rs
Normal file
100
proxmox-apt/src/repositories/release.rs
Normal 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'");
|
||||
}
|
555
proxmox-apt/src/repositories/repository.rs
Normal file
555
proxmox-apt/src/repositories/repository.rs
Normal 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());
|
||||
}
|
273
proxmox-apt/src/repositories/standard.rs
Normal file
273
proxmox-apt/src/repositories/standard.rs
Normal 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 it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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-----
|
@ -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
@ -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-----
|
422
proxmox-apt/tests/repositories.rs
Normal file
422
proxmox-apt/tests/repositories.rs
Normal 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(())
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
# From Debian Administrator's Handbook
|
||||
deb http://packages.falcot.com/ updates/
|
||||
|
||||
deb http://user.name@packages.falcot.com:80/ internal/
|
||||
|
@ -0,0 +1,5 @@
|
||||
# From Debian Administrator's Handbook
|
||||
Types: deb
|
||||
URIs: http://packages.falcot.com/
|
||||
Suites: updates/ internal/
|
||||
|
@ -0,0 +1,4 @@
|
||||
deb http://security.debian.org/debian-security/ bullseye/updates main contrib
|
||||
|
||||
deb https://security.debian.org bullseye/updates main contrib
|
||||
|
30
proxmox-apt/tests/sources.list.d.expected/bad.sources
Normal file
30
proxmox-apt/tests/sources.list.d.expected/bad.sources
Normal 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
|
||||
|
16
proxmox-apt/tests/sources.list.d.expected/case.sources
Normal file
16
proxmox-apt/tests/sources.list.d.expected/case.sources
Normal 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
|
||||
|
10
proxmox-apt/tests/sources.list.d.expected/cdroms.list
Normal file
10
proxmox-apt/tests/sources.list.d.expected/cdroms.list
Normal 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
|
||||
|
4
proxmox-apt/tests/sources.list.d.expected/files.list
Normal file
4
proxmox-apt/tests/sources.list.d.expected/files.list
Normal 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/" /
|
||||
|
10
proxmox-apt/tests/sources.list.d.expected/multiline.sources
Normal file
10
proxmox-apt/tests/sources.list.d.expected/multiline.sources
Normal 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
|
||||
|
@ -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
|
||||
|
@ -0,0 +1,2 @@
|
||||
deb https://enterprise.proxmox.com/debian/pbs bullseye pbs-enterprise
|
||||
|
8
proxmox-apt/tests/sources.list.d.expected/pve-alt.list
Normal file
8
proxmox-apt/tests/sources.list.d.expected/pve-alt.list
Normal 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
|
||||
|
15
proxmox-apt/tests/sources.list.d.expected/pve.list
Normal file
15
proxmox-apt/tests/sources.list.d.expected/pve.list
Normal 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
|
||||
|
7
proxmox-apt/tests/sources.list.d.expected/standard.list
Normal file
7
proxmox-apt/tests/sources.list.d.expected/standard.list
Normal 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
|
||||
|
11
proxmox-apt/tests/sources.list.d.expected/standard.sources
Normal file
11
proxmox-apt/tests/sources.list.d.expected/standard.sources
Normal 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
|
||||
|
4
proxmox-apt/tests/sources.list.d/absolute_suite.list
Normal file
4
proxmox-apt/tests/sources.list.d/absolute_suite.list
Normal 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/
|
5
proxmox-apt/tests/sources.list.d/absolute_suite.sources
Normal file
5
proxmox-apt/tests/sources.list.d/absolute_suite.sources
Normal file
@ -0,0 +1,5 @@
|
||||
# From Debian Administrator's Handbook
|
||||
Types: deb
|
||||
URIs: http://packages.falcot.com/
|
||||
Suites: updates/ internal/
|
||||
|
4
proxmox-apt/tests/sources.list.d/bad-security.list
Normal file
4
proxmox-apt/tests/sources.list.d/bad-security.list
Normal 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
|
||||
|
29
proxmox-apt/tests/sources.list.d/bad.sources
Normal file
29
proxmox-apt/tests/sources.list.d/bad.sources
Normal 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
|
17
proxmox-apt/tests/sources.list.d/case.sources
Normal file
17
proxmox-apt/tests/sources.list.d/case.sources
Normal 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
|
7
proxmox-apt/tests/sources.list.d/cdroms.list
Normal file
7
proxmox-apt/tests/sources.list.d/cdroms.list
Normal 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
|
2
proxmox-apt/tests/sources.list.d/files.list
Normal file
2
proxmox-apt/tests/sources.list.d/files.list
Normal 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/ /
|
11
proxmox-apt/tests/sources.list.d/multiline.sources
Normal file
11
proxmox-apt/tests/sources.list.d/multiline.sources
Normal 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
|
3
proxmox-apt/tests/sources.list.d/options_comment.list
Normal file
3
proxmox-apt/tests/sources.list.d/options_comment.list
Normal 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 :(
|
||||
|
1
proxmox-apt/tests/sources.list.d/pbs-enterprise.list
Normal file
1
proxmox-apt/tests/sources.list.d/pbs-enterprise.list
Normal file
@ -0,0 +1 @@
|
||||
deb https://enterprise.proxmox.com/debian/pbs bullseye pbs-enterprise
|
6
proxmox-apt/tests/sources.list.d/pve-alt.list
Normal file
6
proxmox-apt/tests/sources.list.d/pve-alt.list
Normal 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
|
12
proxmox-apt/tests/sources.list.d/pve.list
Normal file
12
proxmox-apt/tests/sources.list.d/pve.list
Normal 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
|
6
proxmox-apt/tests/sources.list.d/standard.list
Normal file
6
proxmox-apt/tests/sources.list.d/standard.list
Normal 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
|
10
proxmox-apt/tests/sources.list.d/standard.sources
Normal file
10
proxmox-apt/tests/sources.list.d/standard.sources
Normal 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
27
proxmox-openid/Cargo.toml
Normal 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"] }
|
141
proxmox-openid/debian/changelog
Normal file
141
proxmox-openid/debian/changelog
Normal 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
|
61
proxmox-openid/debian/control
Normal file
61
proxmox-openid/debian/control
Normal 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.
|
18
proxmox-openid/debian/copyright
Normal file
18
proxmox-openid/debian/copyright
Normal 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/>.
|
8
proxmox-openid/debian/debcargo.toml
Normal file
8
proxmox-openid/debian/debcargo.toml
Normal 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 = ""
|
116
proxmox-openid/src/auth_state.rs
Normal file
116
proxmox-openid/src/auth_state.rs
Normal 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(())
|
||||
}
|
100
proxmox-openid/src/http_client.rs
Normal file
100
proxmox-openid/src/http_client.rs
Normal 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
253
proxmox-openid/src/lib.rs
Normal 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)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user