Compare commits

..

2 Commits

Author SHA1 Message Date
bdae157caf Bump serde from 1.0.100 to 1.0.101
Some checks reported errors
continuous-integration/drone/push Build was killed
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.100 to 1.0.101.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.100...v1.0.101)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-14 15:26:08 +02:00
d6618a0e61 Bump serde_derive from 1.0.100 to 1.0.101
Bumps [serde_derive](https://github.com/serde-rs/serde) from 1.0.100 to 1.0.101.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.100...v1.0.101)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-14 15:26:08 +02:00
25 changed files with 1885 additions and 6121 deletions

View File

@ -1,31 +0,0 @@
name: Security audit
on:
schedule:
- cron: '0 1 * * *'
push:
paths:
- 'Cargo.toml'
- 'Cargo.lock'
pull_request:
jobs:
security_audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Cache cargo registry
uses: actions/cache@v1
with:
path: /usr/share/rust/.cargo/registry
key: ${{ runner.os }}-cargo-registry
- name: Cache cargo index
uses: actions/cache@v1
with:
path: /usr/share/rust/.cargo/git
key: ${{ runner.os }}-cargo-index
- uses: actions-rs/audit-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,20 +0,0 @@
name: "Nix Build"
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: cachix/install-nix-action@v8
- name: Cache nix store
uses: actions/cache@v1
with:
path: /nix
key: ${{ runner.os }}-nix-store
- uses: cachix/cachix-action@v5
with:
name: hitsofcode
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
attributes: package

View File

@ -1,94 +1,15 @@
name: Rust
on: [push, pull_request]
on: [push]
jobs:
lint:
name: Linting and Formatting Checks
build:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install rustfmt
run: rustup component add rustfmt
- name: Cache cargo registry
uses: actions/cache@v1
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v1
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v1
with:
path: target
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- name: Check Formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- name: Install clippy
run: rustup component add clippy
- name: Clippy Linting
uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
test:
name: Run Tests
runs-on: ${{ matrix.os }}
strategy:
matrix:
# add windows-latest when it is clear why tests are failing
os: [ubuntu-latest, macos-latest]
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Cache cargo registry
uses: actions/cache@v1
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v1
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v1
with:
path: target
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- name: Run Tests
uses: actions-rs/cargo@v1
with:
command: test
- uses: actions/checkout@v1
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose

1
.gitignore vendored
View File

@ -3,4 +3,3 @@
repos
cache
hoc.log
result

3471
Cargo.lock generated

File diff suppressed because it is too large Load Diff

3357
Cargo.nix

File diff suppressed because it is too large Load Diff

View File

@ -1,38 +1,27 @@
[package]
name = "hoc"
version = "0.14.4"
version = "0.11.3"
authors = ["Valentin Brandl <vbrandl@riseup.net>"]
edition = "2018"
build = "build.rs"
[dependencies]
actix-rt = "1.1.1"
actix-slog = "0.2.1"
actix-web = "3.1.0"
badge = "0.3.0"
bytes = "0.6.0"
futures = "0.3.7"
git2 = "0.13.12"
actix-web = "1.0.7"
badge = "0.2.0"
bytes = "0.4.12"
futures = "0.1.29"
git2 = "0.9.1"
lazy_static = "1.4.0"
number_prefix = "0.4.0"
log = "0.4.8"
log4rs = "0.8.3"
number_prefix = "0.3.0"
openssl-probe = "0.1.2"
reqwest = "0.10.8"
serde = "1.0.117"
serde_derive = "1.0.103"
serde_json = "1.0.59"
slog = "2.5.2"
slog-async = "2.5.0"
slog-atomic = "3.0.0"
slog-term = "2.6.0"
structopt = "0.3.20"
tracing = "0.1.21"
tracing-subscriber = "0.2.14"
tracing-actix-web = "0.2.1"
tracing-futures = "0.2.4"
reqwest = "0.9.20"
serde = "1.0.101"
serde_derive = "1.0.101"
serde_json = "1.0.40"
structopt = "0.3.1"
[build-dependencies]
ructe = "0.12.0"
vergen = "3.1.0"
[dev-dependencies]
tempfile = "3.1.0"
ructe = "0.7.2"
vergen = "3.0.4"

View File

@ -29,10 +29,6 @@ https://<host>/<service>/<user>/<repo>/json
There is also an overview page available via `https://<host>/view/<service>/<user>/<repo>`
To delete a repository and the cache from the server, send a `POST` request to
`https://<host>/<service>/<user>/<repo>/delete`. On the overview page, there is a button to perform this operation. It
will respond with a redirect to the overview page so the cache is rebuilt directly.
## Building
The code can be built as a standalone binary, using `cargo` or as a Docker container. Run either
@ -49,10 +45,6 @@ $ docker build .
inside the repository.
I'm currently working on migrating to [nix](https://nixos.org/nix). To get a
development shell, run `nix-shell`, to build the package run `nix-build --attr
package` and to build the Docker image, run `nix-build --attr dockerImage`.
## Running

View File

@ -1 +0,0 @@
{}

View File

@ -1,56 +0,0 @@
{ sources ? import ./nix/sources.nix
, system ? builtins.currentSystem
}:
let
rustOverlay = import "${sources.nixpkgs-mozilla}/rust-overlay.nix";
cargo2nixOverlay = import "${sources.cargo2nix}/overlay";
pkgs = import sources.nixpkgs {
# pkgs = import <nixpkgs> {
inherit system;
overlays = [ cargo2nixOverlay rustOverlay ];
};
rustPkgs = pkgs.rustBuilder.makePackageSet' {
rustChannel = "stable";
packageFun = import ./Cargo.nix;
localPatterns =
[
''^(src|tests)(/.*)?''
''[^/]*\.(rs|toml)$''
# include other directory from the project repository
''^templates(/.*)?''
''^static(/.*)?''
''^.git.*(/.*)?''
];
# packageOverrides
};
in
rec {
inherit rustPkgs;
shell = pkgs.mkShell {
inputsFrom = pkgs.lib.mapAttrsToList (_: pkg: pkg { }) rustPkgs.noBuild.workspace;
nativeBuildInputs = with rustPkgs; [ cargo rustc ];
};
package = (rustPkgs.workspace.hoc {}).overrideAttrs (drv: {
buildInputs = drv.buildInputs or [ ] ++ [ pkgs.git ];
});
dockerImage =
pkgs.dockerTools.buildImage {
name = "vbrandl/hits-of-code";
tag = package.version;
contents =
[
package
pkgs.cacert
pkgs.gitMinimal
];
config = {
Cmd = [ "/bin/hoc" ];
WorkingDir = "/home/hoc";
};
};
}

View File

@ -1,50 +0,0 @@
{
"cargo2nix": {
"branch": "master",
"description": "Convert a Cargo.lock to mkRustCrate statements for import in Nix",
"homepage": "",
"owner": "tenx-tech",
"repo": "cargo2nix",
"rev": "7bc062ccffc41dc7d3759b8b797e8b4f8dd23a15",
"sha256": "1z7xwk1hbp26aydsk3y07riy0ivwqss06n1470mvdl7allfcd1w5",
"type": "tarball",
"url": "https://github.com/tenx-tech/cargo2nix/archive/7bc062ccffc41dc7d3759b8b797e8b4f8dd23a15.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
},
"niv": {
"branch": "master",
"description": "Easy dependency management for Nix projects",
"homepage": "https://github.com/nmattia/niv",
"owner": "nmattia",
"repo": "niv",
"rev": "98c74a80934123cb4c3bf3314567f67311eb711a",
"sha256": "1w8n54hapd4x9f1am33icvngkqns7m3hl9yair38yqq08ffwg0kn",
"type": "tarball",
"url": "https://github.com/nmattia/niv/archive/98c74a80934123cb4c3bf3314567f67311eb711a.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
},
"nixpkgs": {
"branch": "nixpkgs-unstable",
"description": "A read-only mirror of NixOS/nixpkgs tracking the released channels. Send issues and PRs to",
"homepage": "https://github.com/NixOS/nixpkgs",
"owner": "NixOS",
"repo": "nixpkgs-channels",
"rev": "f6bfb371cba2b5a02f200c2747c1fe2c72bd782f",
"sha256": "0y3hlbyvznrpr1d2vxj2511hkjg733wdnxfaib3fgy9i9jr8ivzn",
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs-channels/archive/f6bfb371cba2b5a02f200c2747c1fe2c72bd782f.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
},
"nixpkgs-mozilla": {
"branch": "master",
"description": "mozilla related nixpkgs (extends nixos/nixpkgs repo)",
"homepage": null,
"owner": "mozilla",
"repo": "nixpkgs-mozilla",
"rev": "e912ed483e980dfb4666ae0ed17845c4220e5e7c",
"sha256": "08fvzb8w80bkkabc1iyhzd15f4sm7ra10jn32kfch5klgl0gj3j3",
"type": "tarball",
"url": "https://github.com/mozilla/nixpkgs-mozilla/archive/e912ed483e980dfb4666ae0ed17845c4220e5e7c.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
}
}

View File

@ -1,134 +0,0 @@
# This file has been generated by Niv.
let
#
# The fetchers. fetch_<type> fetches specs of type <type>.
#
fetch_file = pkgs: spec:
if spec.builtin or true then
builtins_fetchurl { inherit (spec) url sha256; }
else
pkgs.fetchurl { inherit (spec) url sha256; };
fetch_tarball = pkgs: spec:
if spec.builtin or true then
builtins_fetchTarball { inherit (spec) url sha256; }
else
pkgs.fetchzip { inherit (spec) url sha256; };
fetch_git = spec:
builtins.fetchGit { url = spec.repo; inherit (spec) rev ref; };
fetch_builtin-tarball = spec:
builtins.trace
''
WARNING:
The niv type "builtin-tarball" will soon be deprecated. You should
instead use `builtin = true`.
$ niv modify <package> -a type=tarball -a builtin=true
''
builtins_fetchTarball { inherit (spec) url sha256; };
fetch_builtin-url = spec:
builtins.trace
''
WARNING:
The niv type "builtin-url" will soon be deprecated. You should
instead use `builtin = true`.
$ niv modify <package> -a type=file -a builtin=true
''
(builtins_fetchurl { inherit (spec) url sha256; });
#
# Various helpers
#
# The set of packages used when specs are fetched using non-builtins.
mkPkgs = sources:
let
sourcesNixpkgs =
import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) {};
hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath;
hasThisAsNixpkgsPath = <nixpkgs> == ./.;
in
if builtins.hasAttr "nixpkgs" sources
then sourcesNixpkgs
else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then
import <nixpkgs> {}
else
abort
''
Please specify either <nixpkgs> (through -I or NIX_PATH=nixpkgs=...) or
add a package called "nixpkgs" to your sources.json.
'';
# The actual fetching function.
fetch = pkgs: name: spec:
if ! builtins.hasAttr "type" spec then
abort "ERROR: niv spec ${name} does not have a 'type' attribute"
else if spec.type == "file" then fetch_file pkgs spec
else if spec.type == "tarball" then fetch_tarball pkgs spec
else if spec.type == "git" then fetch_git spec
else if spec.type == "builtin-tarball" then fetch_builtin-tarball spec
else if spec.type == "builtin-url" then fetch_builtin-url spec
else
abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}";
# Ports of functions for older nix versions
# a Nix version of mapAttrs if the built-in doesn't exist
mapAttrs = builtins.mapAttrs or (
f: set: with builtins;
listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set))
);
# fetchTarball version that is compatible between all the versions of Nix
builtins_fetchTarball = { url, sha256 }@attrs:
let
inherit (builtins) lessThan nixVersion fetchTarball;
in
if lessThan nixVersion "1.12" then
fetchTarball { inherit url; }
else
fetchTarball attrs;
# fetchurl version that is compatible between all the versions of Nix
builtins_fetchurl = { url, sha256 }@attrs:
let
inherit (builtins) lessThan nixVersion fetchurl;
in
if lessThan nixVersion "1.12" then
fetchurl { inherit url; }
else
fetchurl attrs;
# Create the final "sources" from the config
mkSources = config:
mapAttrs (
name: spec:
if builtins.hasAttr "outPath" spec
then abort
"The values in sources.json should not have an 'outPath' attribute"
else
spec // { outPath = fetch config.pkgs name spec; }
) config.sources;
# The "config" used by the fetchers
mkConfig =
{ sourcesFile ? ./sources.json
, sources ? builtins.fromJSON (builtins.readFile sourcesFile)
, pkgs ? mkPkgs sources
}: rec {
# The sources, i.e. the attribute set of spec name to spec
inherit sources;
# The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers
inherit pkgs;
};
in
mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); }

View File

@ -1 +0,0 @@
with import ./. { }; shell

View File

@ -1,124 +1,57 @@
use crate::error::{Error, Result};
use std::{
borrow::Cow,
collections::HashMap,
fs::{create_dir_all, File, OpenOptions},
io::BufReader,
path::Path,
};
/// Enum to indicate the state of the cache
#[derive(Debug)]
pub(crate) enum CacheState<'a> {
/// Current head and cached head are the same
Current {
count: u64,
commits: u64,
cache: Cache<'a>,
},
Current { count: u64, commits: u64 },
/// Cached head is older than current head
Old {
head: String,
cache: Cache<'a>,
},
NoneForBranch(Cache<'a>),
Old(Cache<'a>),
/// No cache was found
No,
}
impl<'a> CacheState<'a> {
#[instrument]
pub(crate) fn read_from_file(
path: impl AsRef<Path> + std::fmt::Debug,
branch: &str,
head: &str,
) -> Result<CacheState<'a>> {
trace!("Reading cache");
pub(crate) fn read_from_file(path: impl AsRef<Path>, head: &str) -> Result<CacheState> {
if path.as_ref().exists() {
let cache: Cache = serde_json::from_reader(BufReader::new(File::open(path)?))?;
Ok(cache
.entries
.get(branch)
.map(|c| {
if c.head == head {
trace!("Cache is up to date");
CacheState::Current {
count: c.count,
commits: c.commits,
// TODO: get rid of clone
cache: cache.clone(),
}
} else {
trace!("Cache is out of date");
CacheState::Old {
head: c.head.to_string(),
// TODO: get rid of clone
cache: cache.clone(),
}
}
if cache.head == head {
Ok(CacheState::Current {
count: cache.count,
commits: cache.commits,
})
// TODO: get rid of clone
.unwrap_or_else(|| CacheState::NoneForBranch(cache.clone())))
} else {
Ok(CacheState::Old(cache))
}
} else {
Ok(CacheState::No)
}
}
#[instrument]
pub(crate) fn calculate_new_cache(
self,
count: u64,
commits: u64,
head: Cow<'a, str>,
branch: &'a str,
) -> Cache<'a> {
trace!("Calculating new cache");
pub(crate) fn calculate_new_cache(self, count: u64, commits: u64, head: Cow<'a, str>) -> Cache {
match self {
CacheState::Old { mut cache, .. } => {
if let Some(mut cache) = cache.entries.get_mut(branch) {
cache.head = head;
cache.count += count;
cache.commits += commits;
}
CacheState::Old(mut cache) => {
cache.head = head;
cache.count += count;
cache.commits += commits;
cache
}
CacheState::Current { cache, .. } => cache,
CacheState::NoneForBranch(mut cache) => {
trace!("Creating new cache for branch");
cache.entries.insert(
branch.into(),
CacheEntry {
head,
count,
commits,
},
);
cache
}
CacheState::No => {
trace!("Creating new cache file");
let mut entries = HashMap::with_capacity(1);
entries.insert(
branch.into(),
CacheEntry {
commits,
head,
count,
},
);
Cache { entries }
}
CacheState::No | CacheState::Current { .. } => Cache {
head,
count,
commits,
},
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[derive(Serialize, Deserialize)]
pub(crate) struct Cache<'a> {
pub entries: HashMap<Cow<'a, str>, CacheEntry<'a>>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub(crate) struct CacheEntry<'a> {
/// HEAD commit ref
pub head: Cow<'a, str>,
/// HoC value
@ -128,9 +61,7 @@ pub(crate) struct CacheEntry<'a> {
}
impl<'a> Cache<'a> {
#[instrument]
pub(crate) fn write_to_file(&self, path: impl AsRef<Path> + std::fmt::Debug) -> Result<()> {
trace!("Persisting cache to disk");
pub(crate) fn write_to_file(&self, path: impl AsRef<Path>) -> Result<()> {
create_dir_all(path.as_ref().parent().ok_or(Error::Internal)?)?;
serde_json::to_writer(
OpenOptions::new()

View File

@ -1,3 +1,10 @@
use crate::{error::Result, statics::OPT};
use log::LevelFilter;
use log4rs::{
append::{console::ConsoleAppender, file::FileAppender},
config::{Appender, Config, Root},
encode::pattern::PatternEncoder,
};
use std::path::PathBuf;
use structopt::StructOpt;
@ -31,11 +38,34 @@ pub(crate) struct Opt {
#[structopt(short = "w", long = "workers", default_value = "4")]
/// Number of worker threads
pub(crate) workers: usize,
#[structopt(
short = "l",
long = "logfile",
parse(from_os_str),
default_value = "./hoc.log"
)]
/// The logfile
pub(crate) logfile: PathBuf,
}
pub(crate) fn init() {
pub(crate) fn init() -> Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info,hoc=info");
// pretty_env_logger::init();
openssl_probe::init_ssl_cert_env_vars();
tracing_subscriber::fmt().init();
let stdout = ConsoleAppender::builder().build();
let file = FileAppender::builder()
.encoder(Box::new(PatternEncoder::new("{d} - {m}{n}")))
.build(&OPT.logfile)
.unwrap();
let config = Config::builder()
.appender(Appender::builder().build("stdout", Box::new(stdout)))
.appender(Appender::builder().build("file", Box::new(file)))
.build(
Root::builder()
.appender("stdout")
.appender("file")
.build(LevelFilter::Info),
)?;
log4rs::init_config(config)?;
Ok(())
}

View File

@ -1,13 +1,10 @@
use crate::error::Result;
use std::{fs::read_dir, path::Path, result::Result as StdResult};
#[instrument]
pub(crate) fn count_repositories<P>(repo_path: P) -> Result<usize>
where
P: AsRef<Path> + std::fmt::Debug,
P: AsRef<Path>,
{
trace!("Counting repositories");
std::fs::create_dir_all(&repo_path)?;
Ok(read_dir(repo_path)?
.filter_map(StdResult::ok)
.filter(|entry| entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false))

View File

@ -14,9 +14,10 @@ pub(crate) enum Error {
Git(git2::Error),
Internal,
Io(std::io::Error),
Log(log::SetLoggerError),
LogBuilder(log4rs::config::Errors),
Parse(std::num::ParseIntError),
Serial(serde_json::Error),
BranchNotFound,
}
impl fmt::Display for Error {
@ -27,9 +28,10 @@ impl fmt::Display for Error {
Error::Git(e) => write!(fmt, "Git({})", e),
Error::Internal => write!(fmt, "Internal Error"),
Error::Io(e) => write!(fmt, "Io({})", e),
Error::Log(e) => write!(fmt, "Log({})", e),
Error::LogBuilder(e) => write!(fmt, "LogBuilder({})", e),
Error::Parse(e) => write!(fmt, "Parse({})", e),
Error::Serial(e) => write!(fmt, "Serial({})", e),
Error::BranchNotFound => write!(fmt, "Repo doesn't have master branch"),
}
}
}
@ -37,24 +39,14 @@ impl fmt::Display for Error {
impl ResponseError for Error {
fn error_response(&self) -> HttpResponse {
let mut buf = Vec::new();
match self {
Error::BranchNotFound => {
templates::p404_no_master(
&mut buf,
VERSION_INFO,
REPO_COUNT.load(Ordering::Relaxed),
)
.unwrap();
HttpResponse::NotFound().content_type("text/html").body(buf)
}
_ => {
templates::p500(&mut buf, VERSION_INFO, REPO_COUNT.load(Ordering::Relaxed))
.unwrap();
HttpResponse::InternalServerError()
.content_type("text/html")
.body(buf)
}
}
templates::p500(&mut buf, VERSION_INFO, REPO_COUNT.load(Ordering::Relaxed)).unwrap();
HttpResponse::InternalServerError()
.content_type("text/html")
.body(buf)
}
fn render_response(&self) -> HttpResponse {
self.error_response()
}
}
@ -72,6 +64,12 @@ impl From<git2::Error> for Error {
}
}
impl From<log::SetLoggerError> for Error {
fn from(err: log::SetLoggerError) -> Self {
Error::Log(err)
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Error::Io(err)
@ -90,6 +88,12 @@ impl From<reqwest::Error> for Error {
}
}
impl From<log4rs::config::Errors> for Error {
fn from(err: log4rs::config::Errors) -> Self {
Error::LogBuilder(err)
}
}
impl From<std::num::ParseIntError> for Error {
fn from(err: std::num::ParseIntError) -> Self {
Error::Parse(err)

View File

@ -5,9 +5,9 @@ extern crate actix_web;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate serde_derive;
extern crate log;
#[macro_use]
extern crate tracing;
extern crate serde_derive;
mod cache;
mod config;
@ -15,37 +15,32 @@ mod count;
mod error;
mod service;
mod statics;
mod template;
#[cfg(test)]
mod tests;
use crate::{
cache::CacheState,
error::{Error, Result},
service::{Bitbucket, FormService, GitHub, Gitlab, Service},
statics::{CLIENT, CSS, FAVICON, OPT, REPO_COUNT, VERSION_INFO},
template::RepoInfo,
};
use actix_web::{
http::header::{CacheControl, CacheDirective, Expires, LOCATION},
middleware::{self, normalize::TrailingSlash},
web, App, HttpResponse, HttpServer, Responder,
error::ErrorBadRequest,
http::header::{CacheControl, CacheDirective, Expires},
middleware, web, App, HttpResponse, HttpServer,
};
use badge::{Badge, BadgeOptions};
use git2::{BranchType, Repository};
use number_prefix::NumberPrefix;
use bytes::Bytes;
use futures::{unsync::mpsc, Future, Stream};
use git2::Repository;
use number_prefix::{NumberPrefix, Prefixed, Standalone};
use std::{
borrow::Cow,
fs::create_dir_all,
io,
path::Path,
process::Command,
sync::atomic::Ordering,
sync::Arc,
time::{Duration, SystemTime},
};
use tracing::Instrument;
include!(concat!(env!("OUT_DIR"), "/templates.rs"));
@ -56,8 +51,7 @@ struct GeneratorForm<'a> {
repo: Cow<'a, str>,
}
#[derive(Debug)]
pub(crate) struct State {
struct State {
repos: String,
cache: String,
}
@ -65,16 +59,10 @@ pub(crate) struct State {
#[derive(Serialize)]
struct JsonResponse<'a> {
head: &'a str,
branch: &'a str,
count: u64,
commits: u64,
}
#[derive(Deserialize, Debug)]
struct BranchQuery {
branch: Option<String>,
}
fn pull(path: impl AsRef<Path>) -> Result<()> {
let repo = Repository::open_bare(path)?;
let mut origin = repo.find_remote("origin")?;
@ -82,17 +70,12 @@ fn pull(path: impl AsRef<Path>) -> Result<()> {
Ok(())
}
fn hoc(repo: &str, repo_dir: &str, cache_dir: &str, branch: &str) -> Result<(u64, String, u64)> {
fn hoc(repo: &str, repo_dir: &str, cache_dir: &str) -> Result<(u64, String, u64)> {
let repo_dir = format!("{}/{}", repo_dir, repo);
let cache_dir = format!("{}/{}.json", cache_dir, repo);
let cache_dir = Path::new(&cache_dir);
let repo = Repository::open_bare(&repo_dir)?;
// TODO: do better...
let head = repo
.find_branch(branch, BranchType::Local)
.map_err(|_| Error::BranchNotFound)?
.into_reference();
let head = format!("{}", head.target().ok_or(Error::BranchNotFound)?);
let head = format!("{}", repo.head()?.target().ok_or(Error::Internal)?);
let mut arg_commit_count = vec!["rev-list".to_string(), "--count".to_string()];
let mut arg = vec![
"log".to_string(),
@ -106,21 +89,20 @@ fn hoc(repo: &str, repo_dir: &str, cache_dir: &str, branch: &str) -> Result<(u64
"-M".to_string(),
"--diff-filter=ACDM".to_string(),
];
let cache = CacheState::read_from_file(&cache_dir, branch, &head)?;
let cache = CacheState::read_from_file(&cache_dir, &head)?;
match &cache {
CacheState::Current { count, commits, .. } => {
info!("Using cache");
CacheState::Current { count, commits } => {
info!("Using cache for {}", repo_dir);
return Ok((*count, head, *commits));
}
CacheState::Old { head, .. } => {
info!("Updating cache");
arg.push(format!("{}..{}", head, branch));
arg_commit_count.push(format!("{}..{}", head, branch));
CacheState::Old(cache) => {
info!("Updating cache for {}", repo_dir);
arg.push(format!("{}..HEAD", cache.head));
arg_commit_count.push(format!("{}..HEAD", cache.head));
}
CacheState::No | CacheState::NoneForBranch(..) => {
info!("Creating cache");
arg.push(branch.to_string());
arg_commit_count.push(branch.to_string());
CacheState::No => {
info!("Creating cache for {}", repo_dir);
arg_commit_count.push("HEAD".to_string());
}
};
arg.push("--".to_string());
@ -149,15 +131,18 @@ fn hoc(repo: &str, repo_dir: &str, cache_dir: &str, branch: &str) -> Result<(u64
})
.sum();
let cache = cache.calculate_new_cache(count, commits, (&head).into(), branch);
let cache = cache.calculate_new_cache(count, commits, (&head).into());
cache.write_to_file(cache_dir)?;
Ok((count, head, commits))
Ok((cache.count, head, commits))
}
async fn remote_exists(url: &str) -> Result<bool> {
let resp = CLIENT.head(url).send().await?;
Ok(resp.status() == reqwest::StatusCode::OK)
fn remote_exists(url: &str) -> impl Future<Item = bool, Error = Error> {
CLIENT
.head(url)
.send()
.map(|resp| resp.status() == reqwest::StatusCode::OK)
.from_err()
}
enum HocResult {
@ -173,139 +158,75 @@ enum HocResult {
NotFound,
}
async fn delete_repo_and_cache<T>(
fn handle_hoc_request<T, F>(
state: web::Data<Arc<State>>,
data: web::Path<(String, String)>,
) -> Result<impl Responder>
where
T: Service,
{
let data = data.into_inner();
let span = info_span!(
"deleting repository and cache",
service = T::domain(),
user = data.0.as_str(),
repo = data.1.as_str()
);
let future = async {
let repo = format!(
"{}/{}/{}",
T::domain(),
data.0.to_lowercase(),
data.1.to_lowercase()
);
info!("Deleting cache and repository");
let cache_dir = format!("{}/{}.json", &state.cache, repo);
let repo_dir = format!("{}/{}", &state.repos, repo);
std::fs::remove_file(&cache_dir).or_else(|e| {
if e.kind() == io::ErrorKind::NotFound {
Ok(())
} else {
Err(e)
}
})?;
std::fs::remove_dir_all(&repo_dir).or_else(|e| {
if e.kind() == io::ErrorKind::NotFound {
Ok(())
} else {
Err(e)
}
})?;
REPO_COUNT.fetch_sub(1, Ordering::Relaxed);
Ok(HttpResponse::TemporaryRedirect()
.header(
LOCATION,
format!("/view/{}/{}/{}", T::url_path(), data.0, data.1),
)
.finish())
};
future.instrument(span).await
}
async fn handle_hoc_request<T, F>(
state: web::Data<Arc<State>>,
data: web::Path<(String, String)>,
branch: &str,
mapper: F,
) -> Result<HttpResponse>
) -> impl Future<Item = HttpResponse, Error = Error>
where
T: Service,
F: Fn(HocResult) -> Result<HttpResponse>,
{
let data = data.into_inner();
let span = info_span!(
"handling hoc calculation",
service = T::domain(),
user = data.0.as_str(),
repo = data.1.as_str(),
branch
);
let future = async {
let repo = format!("{}/{}", data.0.to_lowercase(), data.1.to_lowercase());
let service_path = format!("{}/{}", T::url_path(), repo);
let service_url = format!("{}/{}", T::domain(), repo);
let path = format!("{}/{}", state.repos, service_url);
let url = format!("https://{}", service_url);
let remote_exists = remote_exists(&url).await?;
let file = Path::new(&path);
if !file.exists() {
if !remote_exists {
warn!("Repository does not exist");
return mapper(HocResult::NotFound);
let repo = format!("{}/{}", data.0.to_lowercase(), data.1.to_lowercase());
let service_path = format!("{}/{}", T::domain(), repo);
let path = format!("{}/{}", state.repos, service_path);
let url = format!("https://{}", service_path);
remote_exists(&url)
.and_then(move |remote_exists| {
let file = Path::new(&path);
if !file.exists() {
if !remote_exists {
warn!("Repository does not exist: {}", url);
return Ok(HocResult::NotFound);
}
info!("Cloning {} for the first time", url);
create_dir_all(file)?;
let repo = Repository::init_bare(file)?;
repo.remote_add_fetch("origin", "refs/heads/*:refs/heads/*")?;
repo.remote_set_url("origin", &url)?;
REPO_COUNT.fetch_add(1, Ordering::Relaxed);
}
info!("Cloning for the first time");
create_dir_all(file)?;
let repo = Repository::init_bare(file)?;
repo.remote_add_fetch("origin", "refs/heads/*:refs/heads/*")?;
repo.remote_set_url("origin", &url)?;
REPO_COUNT.fetch_add(1, Ordering::Relaxed);
}
pull(&path)?;
let (hoc, head, commits) = hoc(&service_url, &state.repos, &state.cache, branch)?;
let hoc_pretty = match NumberPrefix::decimal(hoc as f64) {
NumberPrefix::Standalone(hoc) => hoc.to_string(),
NumberPrefix::Prefixed(prefix, hoc) => format!("{:.1}{}", hoc, prefix),
};
let res = HocResult::Hoc {
hoc,
commits,
hoc_pretty,
head,
url,
repo,
service_path,
};
mapper(res)
};
future.instrument(span).await
pull(&path)?;
let (hoc, head, commits) = hoc(&service_path, &state.repos, &state.cache)?;
let hoc_pretty = match NumberPrefix::decimal(hoc as f64) {
Standalone(hoc) => hoc.to_string(),
Prefixed(prefix, hoc) => format!("{:.1}{}", hoc, prefix),
};
Ok(HocResult::Hoc {
hoc,
commits,
hoc_pretty,
head: head.to_string(),
url,
repo,
service_path,
})
})
.and_then(mapper)
}
pub(crate) async fn json_hoc<T: Service>(
fn json_hoc<T: Service>(
state: web::Data<Arc<State>>,
data: web::Path<(String, String)>,
branch: web::Query<BranchQuery>,
) -> Result<HttpResponse> {
let branch = branch.branch.as_deref().unwrap_or("master");
) -> impl Future<Item = HttpResponse, Error = Error> {
let mapper = |r| match r {
HocResult::NotFound => p404(),
HocResult::Hoc {
hoc, head, commits, ..
} => Ok(HttpResponse::Ok().json(JsonResponse {
branch,
head: &head,
count: hoc,
commits,
})),
};
handle_hoc_request::<T, _>(state, data, branch, mapper).await
handle_hoc_request::<T, _>(state, data, mapper)
}
pub(crate) async fn calculate_hoc<T: Service>(
fn calculate_hoc<T: Service>(
state: web::Data<Arc<State>>,
data: web::Path<(String, String)>,
branch: web::Query<BranchQuery>,
) -> Result<HttpResponse> {
let mapper = move |r| match r {
) -> impl Future<Item = HttpResponse, Error = Error> {
let mapper = |r| match r {
HocResult::NotFound => p404(),
HocResult::Hoc { hoc_pretty, .. } => {
let badge_opt = BadgeOptions {
@ -314,8 +235,9 @@ pub(crate) async fn calculate_hoc<T: Service>(
status: hoc_pretty,
};
let badge = Badge::new(badge_opt)?;
// TODO: remove clone
let body = badge.to_svg().as_bytes().to_vec();
let (tx, rx_body) = mpsc::unbounded();
let _ = tx.unbounded_send(Bytes::from(badge.to_svg().as_bytes()));
let expiration = SystemTime::now() + Duration::from_secs(30);
Ok(HttpResponse::Ok()
@ -327,19 +249,16 @@ pub(crate) async fn calculate_hoc<T: Service>(
CacheDirective::NoCache,
CacheDirective::NoStore,
]))
.body(body))
.streaming(rx_body.map_err(|_| ErrorBadRequest("bad request"))))
}
};
let branch = branch.branch.as_deref().unwrap_or("master");
handle_hoc_request::<T, _>(state, data, branch, mapper).await
handle_hoc_request::<T, _>(state, data, mapper)
}
async fn overview<T: Service>(
fn overview<T: Service>(
state: web::Data<Arc<State>>,
data: web::Path<(String, String)>,
branch: web::Query<BranchQuery>,
) -> Result<HttpResponse> {
let branch = branch.branch.as_deref().unwrap_or("master");
) -> impl Future<Item = HttpResponse, Error = Error> {
let mapper = |r| match r {
HocResult::NotFound => p404(),
HocResult::Hoc {
@ -352,32 +271,33 @@ async fn overview<T: Service>(
service_path,
} => {
let mut buf = Vec::new();
let repo_info = RepoInfo {
commit_url: &T::commit_url(&repo, &head),
commits,
domain: &OPT.domain,
head: &head,
hoc,
hoc_pretty: &hoc_pretty,
path: &service_path,
url: &url,
branch,
};
templates::overview(
&mut buf,
VERSION_INFO,
REPO_COUNT.load(Ordering::Relaxed),
repo_info,
&OPT.domain,
&service_path,
&url,
hoc,
&hoc_pretty,
&head,
&T::commit_url(&repo, &head),
commits,
)?;
Ok(HttpResponse::Ok().content_type("text/html").body(buf))
let (tx, rx_body) = mpsc::unbounded();
let _ = tx.unbounded_send(Bytes::from(buf));
Ok(HttpResponse::Ok()
.content_type("text/html")
.streaming(rx_body.map_err(|_| ErrorBadRequest("bad request"))))
}
};
handle_hoc_request::<T, _>(state, data, branch, mapper).await
handle_hoc_request::<T, _>(state, data, mapper)
}
#[get("/")]
async fn index() -> Result<HttpResponse> {
fn index() -> Result<HttpResponse> {
let mut buf = Vec::new();
templates::index(
&mut buf,
@ -389,7 +309,7 @@ async fn index() -> Result<HttpResponse> {
}
#[post("/generate")]
async fn generate(params: web::Form<GeneratorForm<'_>>) -> Result<HttpResponse> {
fn generate(params: web::Form<GeneratorForm>) -> Result<HttpResponse> {
let repo = format!("{}/{}", params.user, params.repo);
let mut buf = Vec::new();
templates::generate(
@ -401,8 +321,12 @@ async fn generate(params: web::Form<GeneratorForm<'_>>) -> Result<HttpResponse>
params.service.service(),
&repo,
)?;
let (tx, rx_body) = mpsc::unbounded();
let _ = tx.unbounded_send(Bytes::from(buf));
Ok(HttpResponse::Ok().content_type("text/html").body(buf))
Ok(HttpResponse::Ok()
.content_type("text/html")
.streaming(rx_body.map_err(|_| ErrorBadRequest("bad request"))))
}
fn p404() -> Result<HttpResponse> {
@ -411,66 +335,48 @@ fn p404() -> Result<HttpResponse> {
Ok(HttpResponse::NotFound().content_type("text/html").body(buf))
}
async fn async_p404() -> Result<HttpResponse> {
p404()
}
#[get("/tacit-css.min.css")]
fn css() -> HttpResponse {
HttpResponse::Ok().content_type("text/css").body(CSS)
}
#[get("/favicon.ico")]
fn favicon32() -> HttpResponse {
HttpResponse::Ok().content_type("image/png").body(FAVICON)
}
async fn start_server() -> std::io::Result<()> {
fn start_server() -> Result<()> {
let interface = format!("{}:{}", OPT.host, OPT.port);
let state = Arc::new(State {
repos: OPT.outdir.display().to_string(),
cache: OPT.cachedir.display().to_string(),
});
HttpServer::new(move || {
Ok(HttpServer::new(move || {
App::new()
.data(state.clone())
.wrap(tracing_actix_web::TracingLogger)
.wrap(middleware::NormalizePath::new(TrailingSlash::Trim))
.wrap(middleware::Logger::default())
.wrap(middleware::NormalizePath)
.service(index)
.service(web::resource("/tacit-css.min.css").route(web::get().to(css)))
.service(web::resource("/favicon.ico").route(web::get().to(favicon32)))
.service(css)
.service(favicon32)
.service(generate)
.service(web::resource("/github/{user}/{repo}").to(calculate_hoc::<GitHub>))
.service(web::resource("/gitlab/{user}/{repo}").to(calculate_hoc::<Gitlab>))
.service(web::resource("/bitbucket/{user}/{repo}").to(calculate_hoc::<Bitbucket>))
.service(
web::resource("/github/{user}/{repo}/delete")
.route(web::post().to(delete_repo_and_cache::<GitHub>)),
)
.service(
web::resource("/gitlab/{user}/{repo}/delete")
.route(web::post().to(delete_repo_and_cache::<Gitlab>)),
)
.service(
web::resource("/bitbucket/{user}/{repo}/delete")
.route(web::post().to(delete_repo_and_cache::<Bitbucket>)),
)
.service(web::resource("/github/{user}/{repo}/json").to(json_hoc::<GitHub>))
.service(web::resource("/gitlab/{user}/{repo}/json").to(json_hoc::<Gitlab>))
.service(web::resource("/bitbucket/{user}/{repo}/json").to(json_hoc::<Bitbucket>))
.service(web::resource("/view/github/{user}/{repo}").to(overview::<GitHub>))
.service(web::resource("/view/gitlab/{user}/{repo}").to(overview::<Gitlab>))
.service(web::resource("/view/bitbucket/{user}/{repo}").to(overview::<Bitbucket>))
.default_service(web::resource("").route(web::get().to(async_p404)))
.service(web::resource("/github/{user}/{repo}").to_async(calculate_hoc::<GitHub>))
.service(web::resource("/gitlab/{user}/{repo}").to_async(calculate_hoc::<Gitlab>))
.service(web::resource("/bitbucket/{user}/{repo}").to_async(calculate_hoc::<Bitbucket>))
.service(web::resource("/github/{user}/{repo}/json").to_async(json_hoc::<GitHub>))
.service(web::resource("/gitlab/{user}/{repo}/json").to_async(json_hoc::<Gitlab>))
.service(web::resource("/bitbucket/{user}/{repo}/json").to_async(json_hoc::<Bitbucket>))
.service(web::resource("/view/github/{user}/{repo}").to_async(overview::<GitHub>))
.service(web::resource("/view/gitlab/{user}/{repo}").to_async(overview::<Gitlab>))
.service(web::resource("/view/bitbucket/{user}/{repo}").to_async(overview::<Bitbucket>))
.default_service(web::resource("").route(web::get().to_async(p404)))
})
.workers(OPT.workers)
.bind(interface)?
.run()
.await
.run()?)
}
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
config::init();
let span = info_span!("hoc", version = env!("CARGO_PKG_VERSION"));
let _ = span.enter();
start_server().instrument(span).await
fn main() -> Result<()> {
config::init()?;
start_server()
}

View File

@ -15,7 +15,7 @@ pub(crate) const CSS: &str = include_str!("../static/tacit-css.min.css");
pub(crate) const FAVICON: &[u8] = include_bytes!("../static/favicon32.png");
lazy_static! {
pub(crate) static ref CLIENT: reqwest::Client = reqwest::Client::new();
pub(crate) static ref CLIENT: reqwest::r#async::Client = reqwest::r#async::Client::new();
pub(crate) static ref OPT: Opt = Opt::from_args();
pub(crate) static ref REPO_COUNT: AtomicUsize =
AtomicUsize::new(count_repositories(&OPT.outdir).unwrap());

View File

@ -1,11 +0,0 @@
pub struct RepoInfo<'a> {
pub commit_url: &'a str,
pub commits: u64,
pub domain: &'a str,
pub head: &'a str,
pub hoc: u64,
pub hoc_pretty: &'a str,
pub path: &'a str,
pub url: &'a str,
pub branch: &'a str,
}

View File

@ -1,66 +0,0 @@
use crate::{
calculate_hoc, index, json_hoc,
service::{Bitbucket, GitHub, Gitlab, Service},
State,
};
use actix_web::{http, test, web, App};
use tempfile::tempdir;
macro_rules! test_app {
($path: expr) => {
test::init_service(App::new().service($path)).await
};
($state: expr, $path: expr) => {
test::init_service(App::new().data($state).service($path)).await
};
}
macro_rules! test_service {
($name: ident, $path: tt, $what: ident) => {
async fn $name<T: 'static + Service>(req_path: &str) {
let repo_dir = dbg!(tempdir().unwrap());
let cache_dir = dbg!(tempdir().unwrap());
let repos = format!("{}/", repo_dir.path().display());
let cache = format!("{}/", cache_dir.path().display());
let state = dbg!(State { repos, cache });
let mut app = test_app!(state, web::resource($path).to($what::<T>));
let req = dbg!(test::TestRequest::with_uri(req_path).to_request());
let resp = dbg!(test::call_service(&mut app, req).await);
assert_eq!(resp.status(), http::StatusCode::OK);
}
};
}
#[actix_rt::test]
async fn test_index() {
let mut app = test::init_service(App::new().service(index)).await;
let req = dbg!(test::TestRequest::with_uri("/").to_request());
let resp = dbg!(test::call_service(&mut app, req).await);
assert_eq!(resp.status(), http::StatusCode::OK);
}
// TODO: fix this test
// #[actix_rt::test]
async fn test_json() {
test_service!(test_json_service, "/service/{user}/{repo}/json", json_hoc);
test_json_service::<Gitlab>("/service/vbrandl/hoc/json").await;
test_json_service::<GitHub>("/service/vbrandl/hoc/json").await;
test_json_service::<Bitbucket>("/service/vbrandl/hoc/json").await;
}
// TODO: fix this test
// #[actix_rt::test]
async fn test_badge() {
test_service!(test_badge_service, "/service/{user}/{repo}", calculate_hoc);
test_badge_service::<Gitlab>("/service/vbrandl/hoc").await;
test_badge_service::<GitHub>("/service/vbrandl/hoc").await;
test_badge_service::<Bitbucket>("/service/vbrandl/hoc").await;
}

View File

@ -45,12 +45,6 @@ would render this badge:
alt="example badge" /></a>
</pre>
<p>
By default, this service assumes the existence of a branch named <code>master</code>. If no branch with that name exists
in your repository or you want a badge for another branch of your repository, just append
<code>?branch=&lt;branch-name&gt;</code> to the URL.
</p>
<p>
You can also request the HoC as JSON by appending <code>/json</code> to the request path. This will return a JSON object
with three fields: <code>count</code> (the HoC value), <code>commits</code> (the number of commits) and

View File

@ -1,17 +1,13 @@
@use super::base;
@use crate::statics::VersionInfo;
@use crate::template::RepoInfo;
@(version_info: VersionInfo, repo_count: usize, repo_info: RepoInfo)
@(version_info: VersionInfo, repo_count: usize, domain: &str, path: &str, url: &str, hoc: u64, hoc_pretty: &str, head: &str, commit_url: &str, commits: u64)
@:base("Hits-of-Code Badges", "Overview", {
<p>
The project <a href="@repo_info.url">@repo_info.url</a> has
<strong>@repo_info.hoc_pretty</strong> (exactly @repo_info.hoc) hits of code at
<a href="@repo_info.commit_url">@repo_info.head</a> on the
<code>@repo_info.branch</code> branch. The repository contains
<strong>@repo_info.commits</strong> commits.
The project <a href="@url">@url</a> has <strong>@hoc_pretty</strong> (exactly @hoc) hits of code at
<a href="@commit_url">@head</a>. The repository contains <strong>@commits</strong> commits.
</p>
<p>
@ -19,11 +15,6 @@ To include the badge in your readme, use the following markdown:
</p>
<pre>
[![Hits-of-Code](https://@repo_info.domain/@repo_info.path?branch=@repo_info.branch)](https://@repo_info.domain/view/@repo_info.path?branch=@repo_info.branch)
[![Hits-of-Code](https://@domain/@path)](https://@domain/view/@path)
</pre>
<form method="post" action="/@repo_info.path/delete">
<button type="submit">Rebuild Cache</button>
</form>
}, version_info, repo_count)

View File

@ -1,16 +0,0 @@
@use super::base;
@use crate::statics::VersionInfo;
@(version_info: VersionInfo, repo_count: usize)
@:base("Branch not Found - Hits-of-Code Badges", "404 - Branch not Found", {
<p>
<big>Sorry</big>. I couldn't find the requested branch of your repositroy. Currently this service assumes the
extistence of a branch named <code>master</code>. If you'd like to request a badge for another branch, you can do so by
attaching <code>?branch=&lt;branch-name&gt;</code> to the request.
</p>
<p>
If you think, this is a mistake on my side, please <a href="mailto:mail+hoc@@vbrandl.net">drop me a mail</a>.
</p>
}, version_info, repo_count)

80
vm.nix
View File

@ -1,80 +0,0 @@
# Nix configuration for a VM to run a custom configured Vim
#
# It is intended as an example of building a VM that builds Vim for testing
# and evaluation purposes. It does not represent a production or secure
# deployment.
{ sources ? import ./nix/sources.nix
, pkgs ? import sources.nixpkgs { }
, callPackage ? pkgs.callPackage
, config
, lib
, ...
}:
# config, pkgs, lib, ... }:
let
hoc = pkgs.callPackage ./default.nix { };
# hoc = cargoNix.rootCrate.build;
in
{
environment = {
systemPackages = with pkgs; [
(
hoc
# import ./default.nix
)
];
};
networking.hostName = "hoc"; # Define your hostname.
system.stateVersion = "19.09"; # The version of NixOS originally installed
# Set security options:
security = {
sudo = {
enable = true; # Enable sudo
wheelNeedsPassword = false; # Allow wheel members to run sudo without a passowrd
};
};
networking.firewall.allowedTCPPorts = [ 80 ];
# List services that you want to enable:
services.openssh = {
enable = true; # Enable the OpenSSH daemon.
#permitRootLogin = "yes"; # Probably want to change this in production
#challengeResponseAuthentication = true; # Probably want to change this in production
#passwordAuthentication = true; # Probably want to change this in production
openFirewall = true;
hostKeys = [
{
path = "/etc/ssh/ssh_host_ed25519_key"; # Generate a key for the vm
type = "ed25519"; # Use the current best key type
}
];
};
# Users of the Vim VM:
users.mutableUsers = false; # Remove any users not defined in here
users.users.root = {
password = "123456"; # Probably want to change this in production
};
# Misc groups:
users.groups.nixos.gid = 1000;
# NixOS users
users.users.nixos = {
isNormalUser = true;
uid = 1000;
group = "nixos";
extraGroups = [ "wheel" ];
password = "123456"; # Probably want to change this in production
};
}