mirror of
https://codeberg.org/h3xx/you-dont-need-pihole.git
synced 2026-06-14 17:55:39 +00:00
Compare commits
6 commits
85d9a5feca
...
266c1bae37
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
266c1bae37 |
||
|
|
9ed09262f2 |
||
|
|
b097d91810 |
||
|
|
83e809ba16 |
||
|
|
9d6932d2d1 |
||
|
|
aba05f1320 |
5 changed files with 277 additions and 42 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
|
@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Require Perl 5.12
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fix warning when writing blocklist to stdout.
|
||||||
|
- Various script clean-ups
|
||||||
|
|
||||||
## [0.2.0] - 2023-06-19
|
## [0.2.0] - 2023-06-19
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
@ -28,3 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [0.1.0] - 2022-11-12
|
## [0.1.0] - 2022-11-12
|
||||||
Initial published version
|
Initial published version
|
||||||
|
|
||||||
|
[Unreleased]: https://codeberg.org/h3xx/you-dont-need-pihole/compare/v0.2.0...HEAD
|
||||||
|
[0.2.0]: https://codeberg.org/h3xx/you-dont-need-pihole/compare/v0.1.0...v0.2.0
|
||||||
|
[0.1.0]: https://codeberg.org/h3xx/you-dont-need-pihole/releases/tag/v0.1.0
|
||||||
|
|
|
||||||
51
README.md
51
README.md
|
|
@ -3,7 +3,9 @@
|
||||||
**Network-wide DNS blocking without extra hardware.**
|
**Network-wide DNS blocking without extra hardware.**
|
||||||
|
|
||||||
This project implements the ad-blocking functionality of a
|
This project implements the ad-blocking functionality of a
|
||||||
[Pi-hole](https://pi-hole.net) without needing any extra hardware.
|
[Pi-hole](https://pi-hole.net) without needing any extra hardware. All you need
|
||||||
|
is a Linux computer on your network. This might also work with a Mac OSX
|
||||||
|
computer, but I haven't tested it.
|
||||||
|
|
||||||
A Pi-hole is a [Raspberry Pi](https://www.raspberrypi.com/) based "black hole"
|
A Pi-hole is a [Raspberry Pi](https://www.raspberrypi.com/) based "black hole"
|
||||||
for Internet advertisements. It works by intercepting and answering DNS queries
|
for Internet advertisements. It works by intercepting and answering DNS queries
|
||||||
|
|
@ -22,12 +24,20 @@ You'll need `dnsmasq` installed for this.
|
||||||
git clone --recursive https://codeberg.org/h3xx/you-dont-need-pihole.git /etc/you-dont-need-pihole
|
git clone --recursive https://codeberg.org/h3xx/you-dont-need-pihole.git /etc/you-dont-need-pihole
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Run `update.sh` to generate the blocklist.
|
2. Run `update.sh` to generate the initial blocklist:
|
||||||
|
|
||||||
3. Update `dnsmasq.d/01-you-dont-need-pihole.conf`, replacing `/etc/you-dont-need-pihole` with wherever the root
|
```sh
|
||||||
of this project is installed to.
|
/etc/you-dont-need-pihole/update.sh
|
||||||
|
```
|
||||||
|
|
||||||
4. Add the configuration directory to `dnsmasq.conf`:
|
> [!IMPORTANT]
|
||||||
|
> If you didn't use `/etc/you-dont-need-pihole` as the installation directory,
|
||||||
|
> update `dnsmasq.d/01-you-dont-need-pihole.conf`, replacing
|
||||||
|
> `/etc/you-dont-need-pihole` with wherever the root of this project is
|
||||||
|
> installed to.
|
||||||
|
|
||||||
|
4. Add You Don't Need Pi Hole's `dnsmasq.d` configuration directory path to
|
||||||
|
`dnsmasq.conf`:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
echo 'conf-dir=/etc/you-dont-need-pihole/dnsmasq.d' >> /etc/dnsmasq.conf
|
echo 'conf-dir=/etc/you-dont-need-pihole/dnsmasq.d' >> /etc/dnsmasq.conf
|
||||||
|
|
@ -36,21 +46,32 @@ echo 'conf-dir=/etc/you-dont-need-pihole/dnsmasq.d' >> /etc/dnsmasq.conf
|
||||||
You can also copy or symlink `dnsmasq.d/01-you-dont-need-pihole.conf` if you need
|
You can also copy or symlink `dnsmasq.d/01-you-dont-need-pihole.conf` if you need
|
||||||
the config to live somewhere else.
|
the config to live somewhere else.
|
||||||
|
|
||||||
5. Restart the `dnsmasq` service.
|
5. Restart the `dnsmasq` service:
|
||||||
|
|
||||||
6. Make sure `53/udp` is unfirewalled.
|
```sh
|
||||||
|
# Debian & Ubuntu, RedHat & CentOS
|
||||||
|
service dnsmasq restart
|
||||||
|
```
|
||||||
|
|
||||||
7. Go into your router settings and change the IP addressed provied via DHCP to
|
6. Make sure `53/udp` is unfirewalled:
|
||||||
be your server's local IP address. [See this thread for a
|
|
||||||
walkthrough.](https://discourse.pi-hole.net/t/how-do-i-configure-my-devices-to-use-pi-hole-as-their-dns-server/245)
|
|
||||||
|
|
||||||
8. Recommended: Configure your local machine to use the local `dnsmasq` daemon
|
```sh
|
||||||
for client DNS queries, i.e. software running on the same server as
|
# Debian & Ubuntu, RedHat & CentOS
|
||||||
`dnsmasq`. This is a good idea because it may save some network traffic
|
sudo firewall-cmd --add-port=53/udp --permanent
|
||||||
|
```
|
||||||
|
|
||||||
|
7. Go into your router settings and change the DNS IP address provided via your
|
||||||
|
router's DHCP responses to be your server's (i.e. the one with You Don't
|
||||||
|
Need Pi Hole) local IP address.
|
||||||
|
[See this thread for a walkthrough.](https://discourse.pi-hole.net/t/how-do-i-configure-my-devices-to-use-pi-hole-as-their-dns-server/245)
|
||||||
|
|
||||||
|
8. Recommended: If you use the You Don't Need Pi Hole machine as a general
|
||||||
|
computer, configure it to use the local `dnsmasq` daemon for client DNS
|
||||||
|
queries. This is a good idea because it may save some network traffic
|
||||||
depending on how your router works. Worst case scenario, it'll do nothing.
|
depending on how your router works. Worst case scenario, it'll do nothing.
|
||||||
|
|
||||||
If your OS uses `dhcpcd` for network configuration, you can add this line to your
|
If your OS uses `dhcpcd` for network configuration (Slackware), you can add
|
||||||
`/etc/dhcpcd.conf`
|
this line to your `/etc/dhcpcd.conf`:
|
||||||
|
|
||||||
```
|
```
|
||||||
static domain_name_servers=127.0.0.1
|
static domain_name_servers=127.0.0.1
|
||||||
|
|
|
||||||
181
dev-t/doc-changelog-links.t
Normal file
181
dev-t/doc-changelog-links.t
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
#!perl
|
||||||
|
use 5.012;
|
||||||
|
use warnings FATAL => 'all';
|
||||||
|
|
||||||
|
use Test::More 'no_plan';
|
||||||
|
|
||||||
|
use FindBin qw//;
|
||||||
|
my $changelog = "$FindBin::Bin/../CHANGELOG.md";
|
||||||
|
my $braced_inner_r = qr/(?:[^\]\\]|\\.)*/;
|
||||||
|
use constant PROJECT_GIT => 'https://codeberg.org/h3xx/you-dont-need-pihole';
|
||||||
|
|
||||||
|
SKIP: {
|
||||||
|
my $clh;
|
||||||
|
unless (open $clh, '<', $changelog) {
|
||||||
|
skip("failed to open changelog file to test: $!");
|
||||||
|
}
|
||||||
|
my @cl_lines = <$clh>;
|
||||||
|
close $clh;
|
||||||
|
|
||||||
|
ok(scalar @cl_lines,
|
||||||
|
'changelog contains at least one line'
|
||||||
|
);
|
||||||
|
|
||||||
|
my %end_links = _link_references(@cl_lines);
|
||||||
|
my @reflinks = _ref_links(@cl_lines);
|
||||||
|
|
||||||
|
my @unresolved = _unresolved_links(\%end_links, \@reflinks);
|
||||||
|
ok(!scalar @unresolved, ChangelogError->new('unresolved links', @unresolved));
|
||||||
|
|
||||||
|
my %unused_links = _unused_links(\%end_links, \@reflinks);
|
||||||
|
ok(!scalar %unused_links, ChangelogError->new('unused end links', values %unused_links));
|
||||||
|
|
||||||
|
my @incorrect_links = _incorrect_end_links(\%end_links, \@reflinks);
|
||||||
|
ok(!scalar @incorrect_links, ChangelogError->new('incorrect end links', @incorrect_links));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _link_references {
|
||||||
|
my @lines = @_;
|
||||||
|
my $end_link_r = qr/^\s*\[(?<key>$braced_inner_r)\]:\s*(?<url>.*?)\s*$/;
|
||||||
|
my %end_links;
|
||||||
|
my $line_number = 0;
|
||||||
|
foreach my $line (@lines) {
|
||||||
|
++$line_number;
|
||||||
|
if ($line =~ $end_link_r) {
|
||||||
|
$end_links{$+{key}} = {
|
||||||
|
line_number => $line_number,
|
||||||
|
url => $+{url},
|
||||||
|
key => $+{key},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return %end_links;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _ref_links {
|
||||||
|
my @lines = @_;
|
||||||
|
my $ref_link_r = qr/\[(?<text>$braced_inner_r)\]\[(?<key>$braced_inner_r)\]/;
|
||||||
|
# Make sure not to catch:
|
||||||
|
# - Normal links "[text](url)"
|
||||||
|
# - End links "[key]: url"
|
||||||
|
my $bare_ref_link_r = qr/\[(?<textkey>$braced_inner_r)\](?!\s*[(:])/;
|
||||||
|
my @reflinks;
|
||||||
|
my $line_number = 0;
|
||||||
|
foreach my $line (@lines) {
|
||||||
|
++$line_number;
|
||||||
|
if ($line =~ $ref_link_r) {
|
||||||
|
push @reflinks, {
|
||||||
|
line_number => $line_number,
|
||||||
|
text => $+{text},
|
||||||
|
key => $+{key},
|
||||||
|
};
|
||||||
|
} elsif ($line =~ $bare_ref_link_r) {
|
||||||
|
push @reflinks, {
|
||||||
|
line_number => $line_number,
|
||||||
|
text => $+{textkey},
|
||||||
|
key => $+{textkey},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return @reflinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _unused_links {
|
||||||
|
my ($end_links, $reflinks) = @_;
|
||||||
|
# Make a copy, lest we destroy data
|
||||||
|
my %unused = %{$end_links};
|
||||||
|
foreach my $link (@{$reflinks}) {
|
||||||
|
my $key = $link->{key};
|
||||||
|
delete $unused{$key};
|
||||||
|
}
|
||||||
|
return %unused;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _unresolved_links {
|
||||||
|
my ($end_links, $reflinks) = @_;
|
||||||
|
my @unresolved;
|
||||||
|
foreach my $link (@{$reflinks}) {
|
||||||
|
my $key = $link->{key};
|
||||||
|
unless (exists $end_links->{$key}) {
|
||||||
|
push @unresolved, $link;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return @unresolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _incorrect_end_links {
|
||||||
|
my ($end_links, $reflinks) = @_;
|
||||||
|
my @versions_in_order;
|
||||||
|
my %versions_seen;
|
||||||
|
foreach my $link (@{$reflinks}) {
|
||||||
|
my $key = $link->{key};
|
||||||
|
if (_is_version($key) && !exists $versions_seen{$key}) {
|
||||||
|
$versions_seen{$key} = 1;
|
||||||
|
push @versions_in_order, $key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
my %expected_links = (
|
||||||
|
Unreleased => _make_tag_link("v$versions_in_order[0]", 'HEAD'),
|
||||||
|
$versions_in_order[-1] => _make_tag_link("v$versions_in_order[-1]"),
|
||||||
|
);
|
||||||
|
foreach my $idx (0 .. ($#versions_in_order - 1)) {
|
||||||
|
my $this_version = $versions_in_order[$idx];
|
||||||
|
my $last_version = $versions_in_order[$idx + 1];
|
||||||
|
$expected_links{$this_version} = _make_tag_link("v$last_version", "v$this_version");
|
||||||
|
}
|
||||||
|
|
||||||
|
my @incorrect_links;
|
||||||
|
while (my ($key, $link) = each %{$end_links}) {
|
||||||
|
if (exists $expected_links{$key}) {
|
||||||
|
my $got = $link->{url};
|
||||||
|
my $expected = $expected_links{$key};
|
||||||
|
if ($got ne $expected) {
|
||||||
|
$link->{url_expected} = $expected;
|
||||||
|
push @incorrect_links, $link;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return @incorrect_links;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _make_tag_link {
|
||||||
|
my ($before_tag, $after_tag) = @_;
|
||||||
|
unless (defined $after_tag) {
|
||||||
|
return sprintf '%s/releases/tag/%s', PROJECT_GIT, $before_tag;
|
||||||
|
}
|
||||||
|
return sprintf '%s/compare/%s...%s', PROJECT_GIT, $before_tag, $after_tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _is_version {
|
||||||
|
my $ver = shift;
|
||||||
|
return $ver =~ /^[0-9.]+$/;
|
||||||
|
}
|
||||||
|
|
||||||
|
package ChangelogError;
|
||||||
|
use 5.012;
|
||||||
|
use warnings FATAL => 'all';
|
||||||
|
use overload '""' => '_as_string';
|
||||||
|
|
||||||
|
sub new {
|
||||||
|
my ($class, $name, @data) = @_;
|
||||||
|
return bless {
|
||||||
|
_name => $name,
|
||||||
|
_data => \@data,
|
||||||
|
}, $class;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _as_string {
|
||||||
|
my $self = shift;
|
||||||
|
my @all = ($self->{_name});
|
||||||
|
foreach my $datum (@{$self->{_data}}) {
|
||||||
|
my @out;
|
||||||
|
foreach my $key (qw/ line_number key text url url_expected /) {
|
||||||
|
if (defined $datum->{$key}) {
|
||||||
|
push @out, "$key: $datum->{$key}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
push @all, '- ' . (join ', ', @out);
|
||||||
|
}
|
||||||
|
return join "\n", @all;
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
use 5.012;
|
use 5.012;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
|
||||||
|
use Carp qw/ croak /;
|
||||||
use Getopt::Long qw/ GetOptions :config bundling no_getopt_compat no_ignore_case /;
|
use Getopt::Long qw/ GetOptions :config bundling no_getopt_compat no_ignore_case /;
|
||||||
use FindBin qw//;
|
use FindBin qw//;
|
||||||
|
|
||||||
|
|
@ -23,23 +24,26 @@ my %domains;
|
||||||
my $dupes = 0;
|
my $dupes = 0;
|
||||||
my $skip = 0;
|
my $skip = 0;
|
||||||
my $removed_allowed = 0;
|
my $removed_allowed = 0;
|
||||||
sub add_domain_list {
|
|
||||||
|
sub _add_domain_list {
|
||||||
my $file = shift;
|
my $file = shift;
|
||||||
foreach my $line (read_stripped($file)) {
|
foreach my $line (_read_stripped($file)) {
|
||||||
my $domain = lc $line;
|
my $domain = lc $line;
|
||||||
if (defined $domains{$domain}) {
|
if (defined $domains{$domain}) {
|
||||||
++$dupes;
|
++$dupes;
|
||||||
}
|
}
|
||||||
$domains{$domain} = 1;
|
$domains{$domain} = 1;
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub add_host_file {
|
sub _add_host_file {
|
||||||
my $file = shift;
|
my $file = shift;
|
||||||
foreach my $line (read_stripped($file)) {
|
foreach my $line (_read_stripped($file)) {
|
||||||
my @parts = split /\s+/, $line;
|
my @parts = split /\s+/, $line;
|
||||||
die "Malformed line in $file: $line; @parts"
|
if (@parts < 2) {
|
||||||
unless @parts > 1;
|
croak("Malformed line in $file: $line; @parts");
|
||||||
|
}
|
||||||
if (lc $parts[0] eq lc $parts[1]) {
|
if (lc $parts[0] eq lc $parts[1]) {
|
||||||
++$skip;
|
++$skip;
|
||||||
next;
|
next;
|
||||||
|
|
@ -54,20 +58,30 @@ sub add_host_file {
|
||||||
}
|
}
|
||||||
$domains{$domain} = 1;
|
$domains{$domain} = 1;
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub read_stripped {
|
sub _read_stripped {
|
||||||
my $file = shift;
|
my $file = shift;
|
||||||
|
|
||||||
open my $fni, '<', $file
|
my @stripped_lines;
|
||||||
or die "Failed to open file $file for reading: $!";
|
my $add_stripped = sub {
|
||||||
|
my $line = shift;
|
||||||
map {
|
chomp $line;
|
||||||
chomp;
|
|
||||||
# Strip whitespace and comments
|
# Strip whitespace and comments
|
||||||
s/^\s+|\s+$|\s*#.*$//;
|
$line =~ s/^\s+|\s+$|\s*#.*$//;
|
||||||
$_ || ()
|
if ($line) {
|
||||||
} <$fni>;
|
push @stripped_lines, $line;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
open my $fhi, '<', $file
|
||||||
|
or croak("Failed to open file $file for reading: $!");
|
||||||
|
|
||||||
|
while (my $line = <$fhi>) {
|
||||||
|
$add_stripped->($line);
|
||||||
|
}
|
||||||
|
close $fhi;
|
||||||
|
return @stripped_lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
MAIN: {
|
MAIN: {
|
||||||
|
|
@ -87,38 +101,36 @@ MAIN: {
|
||||||
my @allow_lists = glob "$workdir/allowlists/*.domains";
|
my @allow_lists = glob "$workdir/allowlists/*.domains";
|
||||||
|
|
||||||
foreach my $listfile (@domain_lists) {
|
foreach my $listfile (@domain_lists) {
|
||||||
add_domain_list($listfile);
|
_add_domain_list($listfile);
|
||||||
}
|
}
|
||||||
foreach my $hostfile (@hosts_lists) {
|
foreach my $hostfile (@hosts_lists) {
|
||||||
add_host_file($hostfile);
|
_add_host_file($hostfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
# Apply allowlists
|
# Apply allowlists
|
||||||
my @allow_domains;
|
my @allow_domains;
|
||||||
foreach my $allowlist (@allow_lists) {
|
foreach my $allowlist (@allow_lists) {
|
||||||
push @allow_domains, read_stripped($allowlist);
|
push @allow_domains, _read_stripped($allowlist);
|
||||||
}
|
}
|
||||||
my $before = %domains;
|
my $before = %domains;
|
||||||
delete %domains{@allow_domains};
|
delete %domains{@allow_domains};
|
||||||
# Count number removed
|
# Count number removed
|
||||||
$removed_allowed = $before - %domains;
|
$removed_allowed = $before - %domains;
|
||||||
|
|
||||||
my $written = 0;
|
my @block_ip = sort split /\s+/, $block_ip;
|
||||||
my $fho = \*STDOUT;
|
my $fho = \*STDOUT;
|
||||||
if (defined $out && length $out) {
|
if (defined $out && length $out) {
|
||||||
open $fho, '>', $out
|
open $fho, '>', $out
|
||||||
or die "Failed to open file $out for writing: $!";
|
or croak("Failed to open file $out for writing: $!");
|
||||||
}
|
}
|
||||||
my @block_ip = sort split /\s+/, $block_ip;
|
foreach my $domain (sort keys %domains) {
|
||||||
print $fho map {
|
print $fho map {
|
||||||
++$written;
|
|
||||||
my $domain = $_;
|
|
||||||
map {
|
|
||||||
"$_ $domain\n"
|
"$_ $domain\n"
|
||||||
} @block_ip;
|
} @block_ip;
|
||||||
} sort keys %domains;
|
}
|
||||||
|
close $fho;
|
||||||
|
|
||||||
printf STDERR "%d domains written to %s from\n", $written, $out // 'STDOUT';
|
printf STDERR "%d domains written to %s from\n", (scalar %domains), $out // 'STDOUT';
|
||||||
printf STDERR " - %d .domains files\n", (scalar @domain_lists);
|
printf STDERR " - %d .domains files\n", (scalar @domain_lists);
|
||||||
printf STDERR " - %d .hosts files\n", (scalar @hosts_lists);
|
printf STDERR " - %d .hosts files\n", (scalar @hosts_lists);
|
||||||
if ($dupes) {
|
if ($dupes) {
|
||||||
|
|
|
||||||
10
util/run-dev-tests.sh
Executable file
10
util/run-dev-tests.sh
Executable file
|
|
@ -0,0 +1,10 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
WORKDIR=${0%/*}
|
||||||
|
BASEDIR="$WORKDIR/.."
|
||||||
|
cd "$BASEDIR" || exit
|
||||||
|
|
||||||
|
if ! prove 'dev-t'; then
|
||||||
|
printf 'Developer tests failed!\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
Loading…
Add table
Add a link
Reference in a new issue