Add initial Linux support #150

Key features of Linux support:
- It supports python 3 scripts execution.
- It supports Flatpak and Snap installation for software
  clean-up/configurations.
- Extensive documentation.
This commit is contained in:
undergroundwires
2023-07-30 22:54:24 +02:00
parent e8199932b4
commit c404dfebe2
19 changed files with 3847 additions and 36 deletions

View File

@@ -21,8 +21,9 @@ A clear and concise description of what the bug is.
<!-- <!--
Which OS are you using? What version of OS you were using? Which OS are you using? What version of OS you were using?
On Windows you can find it using "Start button" > "Settings" > "System" > "About". On Windows: Open "Start button" > "Settings" > "System" > "About".
On macOS you can find it using "Apple menu (top left corner)" > "About This Mac". On macOS: Open "Apple menu (top left corner)" > "About This Mac".
On Linux: Open terminal > type: lsb_release -a > copy paste the result.
--> -->
### Reproduction steps ### Reproduction steps

View File

@@ -14,7 +14,7 @@ You could alternatively send a PR directly (see CONTRIBUTING.md).
<!-- <!--
Which OS will the new script configure? Which OS will the new script configure?
Either "Windows" or "macOS". One of the supported OSes: "Windows", "macOS" or "Linux".
--> -->
### Name ### Name
@@ -31,9 +31,11 @@ E.g. "Disable webcam telemetry"
Code that will be executed when script is selected. Code that will be executed when script is selected.
Try to keep it as simple and backwards-compatible as possible. Try to keep it as simple and backwards-compatible as possible.
Allowed languages: Allowed languages:
- macOS: bash (sh)
- Windows: PowerShell (ps1) or batchfile - Windows: PowerShell (ps1) or batchfile
- 💡 Prioritize the one that's simpler, batchfile if similar. - 💡 Prioritize the one that's simpler, batchfile if similar.
- macOS: bash (sh)
- Linux: bash (sh) or Python 3
- 💡 Prioritize the one that's simpler, bash if similar.
--> -->
### Revert code ### Revert code

View File

@@ -1,6 +1,6 @@
# privacy.sexy # privacy.sexy
> Enforce privacy & security best-practices on Windows and macOS, because privacy is sexy 🍑🍆 > Enforce privacy & security best-practices on Windows, macOS and Linux, because privacy is sexy 🍑🍆
<!-- markdownlint-disable MD033 --> <!-- markdownlint-disable MD033 -->
<p align="center"> <p align="center">
@@ -120,7 +120,7 @@ Online version does not require to run any software on your computer. Offline ve
- **Reversible**. Revert if something feels wrong. - **Reversible**. Revert if something feels wrong.
- **Accessible**. No need to run any compiled software on your computer with web version. - **Accessible**. No need to run any compiled software on your computer with web version.
- **Open**. What you see as code in this repository is what you get. The application itself, its infrastructure and deployments are open-source and automated thanks to [bump-everywhere](https://github.com/undergroundwires/bump-everywhere). - **Open**. What you see as code in this repository is what you get. The application itself, its infrastructure and deployments are open-source and automated thanks to [bump-everywhere](https://github.com/undergroundwires/bump-everywhere).
- **Tested.** A lot of tests. Automated and manual. Community-testing and verification. Stability improvements comes before new features. - **Tested**. A lot of tests. Automated and manual. Community-testing and verification. Stability improvements comes before new features.
- **Extensible**. Effortlessly [extend scripts](./CONTRIBUTING.md#extend-scripts) with a custom designed [templating language](./docs/templating.md). - **Extensible**. Effortlessly [extend scripts](./CONTRIBUTING.md#extend-scripts) with a custom designed [templating language](./docs/templating.md).
- **Portable and simple**. Every script is independently executable without cross-dependencies. - **Portable and simple**. Every script is independently executable without cross-dependencies.

View File

@@ -2,7 +2,7 @@
"name": "privacy.sexy", "name": "privacy.sexy",
"version": "0.11.4", "version": "0.11.4",
"private": true, "private": true,
"description": "Enforce privacy & security best-practices on Windows and macOS, because privacy is sexy 🍑🍆", "description": "Enforce privacy & security best-practices on Windows, macOS and Linux, because privacy is sexy 🍑🍆",
"author": "undergroundwires", "author": "undergroundwires",
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Privacy is sexy 🍑🍆 - Enforce privacy & security on Windows and macOS</title> <title>Privacy is sexy 🍑🍆 - Enforce privacy & security on Windows, macOS and Linux</title>
<meta name="robots" content="index,follow" /> <meta name="robots" content="index,follow" />
<meta name="description" content="Web tool to generate scripts for enforcing privacy & security best-practices such as stopping data collection of Windows and different softwares on it."/> <meta name="description" content="Web tool to generate scripts for enforcing privacy & security best-practices such as stopping data collection of Windows and different softwares on it."/>
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> <link rel="icon" href="<%= BASE_URL %>favicon.ico">

View File

@@ -4,6 +4,7 @@ import { IProjectInformation } from '@/domain/IProjectInformation';
import { ICategoryCollection } from '@/domain/ICategoryCollection'; import { ICategoryCollection } from '@/domain/ICategoryCollection';
import WindowsData from '@/application/collections/windows.yaml'; import WindowsData from '@/application/collections/windows.yaml';
import MacOsData from '@/application/collections/macos.yaml'; import MacOsData from '@/application/collections/macos.yaml';
import LinuxData from '@/application/collections/linux.yaml';
import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser'; import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser';
import { Application } from '@/domain/Application'; import { Application } from '@/domain/Application';
import { parseCategoryCollection } from './CategoryCollectionParser'; import { parseCategoryCollection } from './CategoryCollectionParser';
@@ -28,7 +29,7 @@ const CategoryCollectionParser: CategoryCollectionParserType = (file, info) => {
}; };
const PreParsedCollections: readonly CollectionData [] = [ const PreParsedCollections: readonly CollectionData [] = [
WindowsData, MacOsData, WindowsData, MacOsData, LinuxData,
]; ];
function validateCollectionsData(collections: readonly CollectionData[]) { function validateCollectionsData(collections: readonly CollectionData[]) {

View File

@@ -3,5 +3,5 @@ import { ILanguageSyntax } from '@/application/Parser/Script/Validation/Syntax/I
export class ShellScriptSyntax implements ILanguageSyntax { export class ShellScriptSyntax implements ILanguageSyntax {
public readonly commentDelimiters = ['#']; public readonly commentDelimiters = ['#'];
public readonly commonCodeParts = ['(', ')', 'else', 'fi']; public readonly commonCodeParts = ['(', ')', 'else', 'fi', 'done'];
} }

File diff suppressed because it is too large Load Diff

View File

@@ -26,6 +26,8 @@ export class CodeRunner {
function getExecuteCommand(scriptPath: string, environment: Environment): string { function getExecuteCommand(scriptPath: string, environment: Environment): string {
switch (environment.os) { switch (environment.os) {
case OperatingSystem.Linux:
return `x-terminal-emulator -e '${scriptPath}'`;
case OperatingSystem.macOS: case OperatingSystem.macOS:
return `open -a Terminal.app ${scriptPath}`; return `open -a Terminal.app ${scriptPath}`;
// Another option with graphical sudo would be // Another option with graphical sudo would be

View File

@@ -0,0 +1,93 @@
import { OperatingSystem } from '@/domain/OperatingSystem';
import { InstructionsBuilder } from './InstructionsBuilder';
export class LinuxInstructionsBuilder extends InstructionsBuilder {
constructor() {
super(OperatingSystem.Linux);
super
.withStep(() => ({
action: {
instruction: 'Download the file.',
details: 'You should have already been prompted to save the script file.'
+ '<br/>If this was not the case or you did not save the script when prompted,'
+ '<br/>please try to download your script file again.',
},
}))
.withStep(() => ({
action: {
instruction: 'Open terminal.',
details:
'Opening terminal changes based on the distro you run.'
+ '<br/>You may search for "Terminal" in your application launcher to find it.'
+ '<br/>'
+ '<br/>Alternatively use terminal shortcut for your distro if it has one by default:'
+ '<ul>'
+ '<li><code>Ctrl-Alt-T</code>: Ubuntu, CentOS, Linux Mint, Elementary OS, ubermix, Kali…</li>'
+ '<li><code>Super-T</code>: Pop!_OS…</li>'
+ '<li><code>Alt-T</code>: Parrot OS…</li>'
+ '<li><code>Ctrl-Alt-Insert</code>: Bodhi Linux…</li>'
+ '</ul>'
+ '<br/>'
,
},
}))
.withStep(() => ({
action: {
instruction: 'Navigate to the folder where you downloaded the file e.g.:',
},
code: {
instruction: 'cd ~/Downloads',
details: 'Press on <code>enter/return</code> key after running the command.'
+ '<br/>If the file is not downloaded on Downloads folder,'
+ '<br/>change <code>Downloads</code> to path where the file is downloaded.'
+ '<br/>'
+ '<br/>This command means:'
+ '<ul>'
+ '<li><code>cd</code> will change the current folder.</li>'
+ '<li><code>~</code> is the user home directory.</li>'
+ '</ul>',
},
}))
.withStep((data) => ({
action: {
instruction: 'Give the file execute permissions:',
},
code: {
instruction: `chmod +x ${data.fileName}`,
details: 'Press on <code>enter/return</code> key after running the command.<br/>'
+ 'It will make the file executable. <br/>'
+ 'If you use desktop environment you can alternatively (instead of running the command):'
+ '<ol>'
+ '<li>Locate the file using your file manager.</li>'
+ '<li>Right click on the file, select "Properties".</li>'
+ '<li>Go to "Permissions" and check "Allow executing file as program".</li>'
+ '</ol>'
+ '<br/>These GUI steps and name of options may change depending on your file manager.'
,
},
}))
.withStep((data) => ({
action: {
instruction: 'Execute the file:',
},
code: {
instruction: `./${data.fileName}`,
details:
'If you have desktop environment, instead of running this command you can alternatively:'
+ '<ol>'
+ '<li>Locate the file using your file manager.</li>'
+ '<li>Right click on the file, select "Run as program".</li>'
+ '</ol>'
,
},
}))
.withStep(() => ({
action: {
instruction: 'If asked, enter your administrator password.',
details: 'As you type, your password will be hidden but the keys are still registered, so keep typing.'
+ '<br/>Press on <code>enter/return</code> key after typing your password.'
+ '<br/>Administrator privileges are required to configure OS.',
},
}));
}
}

View File

@@ -2,9 +2,11 @@ import { OperatingSystem } from '@/domain/OperatingSystem';
import { InstructionsBuilder } from './Data/InstructionsBuilder'; import { InstructionsBuilder } from './Data/InstructionsBuilder';
import { MacOsInstructionsBuilder } from './Data/MacOsInstructionsBuilder'; import { MacOsInstructionsBuilder } from './Data/MacOsInstructionsBuilder';
import { IInstructionListData } from './InstructionListData'; import { IInstructionListData } from './InstructionListData';
import { LinuxInstructionsBuilder } from './Data/LinuxInstructionsBuilder';
const builders = new Map<OperatingSystem, InstructionsBuilder>([ const builders = new Map<OperatingSystem, InstructionsBuilder>([
[OperatingSystem.macOS, new MacOsInstructionsBuilder()], [OperatingSystem.macOS, new MacOsInstructionsBuilder()],
[OperatingSystem.Linux, new LinuxInstructionsBuilder()],
]); ]);
export function hasInstructions(os: OperatingSystem) { export function hasInstructions(os: OperatingSystem) {

View File

@@ -50,6 +50,7 @@ function renderOsName(os: OperatingSystem): string {
switch (os) { switch (os) {
case OperatingSystem.Windows: return 'Windows'; case OperatingSystem.Windows: return 'Windows';
case OperatingSystem.macOS: return 'macOS'; case OperatingSystem.macOS: return 'macOS';
case OperatingSystem.Linux: return 'Linux (preview)';
default: throw new RangeError(`Cannot render os name: ${OperatingSystem[os]}`); default: throw new RangeError(`Cannot render os name: ${OperatingSystem[os]}`);
} }
} }

View File

@@ -24,7 +24,7 @@ function beatifyAutoLinks(content: string): string {
if (!content) { if (!content) {
return content; return content;
} }
return content.replaceAll(/(?<!\]\(|\[\d+\]:\s+|https?\S+)((?:https?):\/\/[^\s\])]*)(?:[\])](?!\()|$|\s)/gm, (_$, urlMatch) => { return content.replaceAll(/(?<!\]\(|\[\d+\]:\s+|https?\S+|`)((?:https?):\/\/[^\s\])]*)(?:[\])](?!\()|$|\s)/gm, (_$, urlMatch) => {
return toReadableLink(urlMatch); return toReadableLink(urlMatch);
}); });
} }

View File

@@ -56,7 +56,7 @@ function hasDesktopVersion(os: OperatingSystem): boolean {
function getOperatingSystemName(os: OperatingSystem): string { function getOperatingSystemName(os: OperatingSystem): string {
switch (os) { switch (os) {
case OperatingSystem.Linux: case OperatingSystem.Linux:
return 'Linux'; return 'Linux (preview)';
case OperatingSystem.macOS: case OperatingSystem.macOS:
return 'macOS'; return 'macOS';
case OperatingSystem.Windows: case OperatingSystem.Windows:

View File

@@ -1,7 +1,7 @@
<template> <template>
<div id="container"> <div id="container">
<h1 class="child title" >{{ title }}</h1> <h1 class="child title" >{{ title }}</h1>
<h2 class="child subtitle">Enforce privacy &amp; security on Windows and macOS</h2> <h2 class="child subtitle">Enforce privacy &amp; security on Windows, macOS and Linux</h2>
</div> </div>
</template> </template>

View File

@@ -33,17 +33,18 @@ describe('collections', () => {
}); });
function collectUniqueUrls(app: IApplication): string[] { function collectUniqueUrls(app: IApplication): string[] {
return app return [ // Get all nodes
.collections ...app.collections.flatMap((c) => c.getAllCategories()),
.flatMap((a) => a.getAllScripts()) ...app.collections.flatMap((c) => c.getAllScripts()),
.flatMap((script) => script.docs?.flatMap((doc) => parseUrls(doc))) ]
// Get all docs
.flatMap((documentable) => documentable.docs)
// Parse all URLs
.flatMap((docString) => docString.match(/(https?:\/\/[^\s]+)/g) || [])
// Remove duplicates
.filter((url, index, array) => array.indexOf(url) === index); .filter((url, index, array) => array.indexOf(url) === index);
} }
function parseUrls(text: string): string[] {
return text?.match(/\bhttps?:\/\/\S+/gi) ?? [];
}
function printUrls(statuses: IUrlStatus[]): string { function printUrls(statuses: IUrlStatus[]): string {
/* eslint-disable prefer-template */ /* eslint-disable prefer-template */
return '\n' return '\n'

View File

@@ -12,16 +12,17 @@ describe('MarkdownRenderer', () => {
it(`${node.nodeLabel}`, () => { it(`${node.nodeLabel}`, () => {
// act // act
const html = renderer.render(node.docs); const html = renderer.render(node.docs);
const result = validateHtml(html);
// assert // assert
expect(isValidHtml(html)); expect(result.isValid, result.generatedHtml);
}); });
} }
}); });
}); });
interface IDocumentableNode { interface IDocumentableNode {
nodeLabel: string readonly nodeLabel: string
docs: string readonly docs: string
} }
function* collectAllDocumentableNodes(): Generator<IDocumentableNode> { function* collectAllDocumentableNodes(): Generator<IDocumentableNode> {
const app = parseApplication(); const app = parseApplication();
@@ -40,7 +41,16 @@ function* collectAllDocumentableNodes(): Generator<IDocumentableNode> {
} }
} }
function isValidHtml(value: string): boolean { interface IHTMLValidationResult {
const doc = new window.DOMParser().parseFromString(value, 'text/html'); readonly isValid: boolean;
return Array.from(doc.body.childNodes).some((node) => node.nodeType === 1); readonly generatedHtml: string;
}
function validateHtml(value: string): IHTMLValidationResult {
const doc = new window.DOMParser()
.parseFromString(value, 'text/html');
return {
isValid: Array.from(doc.body.childNodes).some((node) => node.nodeType === 1),
generatedHtml: doc.body.innerHTML,
};
} }

View File

@@ -5,6 +5,7 @@ import { parseProjectInformation } from '@/application/Parser/ProjectInformation
import { CategoryCollectionParserType, parseApplication } from '@/application/Parser/ApplicationParser'; import { CategoryCollectionParserType, parseApplication } from '@/application/Parser/ApplicationParser';
import WindowsData from '@/application/collections/windows.yaml'; import WindowsData from '@/application/collections/windows.yaml';
import MacOsData from '@/application/collections/macos.yaml'; import MacOsData from '@/application/collections/macos.yaml';
import LinuxData from '@/application/collections/linux.yaml';
import { IProjectInformation } from '@/domain/IProjectInformation'; import { IProjectInformation } from '@/domain/IProjectInformation';
import { ProjectInformation } from '@/domain/ProjectInformation'; import { ProjectInformation } from '@/domain/ProjectInformation';
import { ICategoryCollection } from '@/domain/ICategoryCollection'; import { ICategoryCollection } from '@/domain/ICategoryCollection';
@@ -101,7 +102,7 @@ describe('ApplicationParser', () => {
}); });
it('defaults to expected data', () => { it('defaults to expected data', () => {
// arrange // arrange
const expected = [WindowsData, MacOsData]; const expected = [WindowsData, MacOsData, LinuxData];
const parserSpy = new CategoryCollectionParserSpy(); const parserSpy = new CategoryCollectionParserSpy();
const parserMock = parserSpy.mockParser(); const parserMock = parserSpy.mockParser();
// act // act

View File

@@ -77,13 +77,20 @@ describe('CodeRunner', () => {
describe('executes as expected', () => { describe('executes as expected', () => {
// arrange // arrange
const filePath = 'expected-file-path'; const filePath = 'expected-file-path';
const testData = [{ const testData = [
{
os: OperatingSystem.Windows, os: OperatingSystem.Windows,
expected: filePath, expected: filePath,
}, { },
{
os: OperatingSystem.macOS, os: OperatingSystem.macOS,
expected: `open -a Terminal.app ${filePath}`, expected: `open -a Terminal.app ${filePath}`,
}]; },
{
os: OperatingSystem.Linux,
expected: `x-terminal-emulator -e '${filePath}'`,
},
];
for (const data of testData) { for (const data of testData) {
it(`returns ${data.expected} on ${OperatingSystem[data.os]}`, async () => { it(`returns ${data.expected} on ${OperatingSystem[data.os]}`, async () => {
const context = new TestContext(); const context = new TestContext();