add initial macOS support #40

This commit is contained in:
undergroundwires
2021-01-13 16:31:20 +01:00
parent 2428de23ee
commit 8a8b7319d5
99 changed files with 2663 additions and 1135 deletions

View File

@@ -36,9 +36,8 @@ export class ApplicationContext implements IApplicationContext {
throw new Error(`os "${OperatingSystem[os]}" is not defined in application`);
}
const event: IApplicationContextChangedEvent = {
newState: this.state,
newCollection: this.collection,
newOs: os,
newState: this.states[os],
oldState: this.states[this.currentOs],
};
this.contextChanged.notify(event);
this.currentOs = os;

View File

@@ -24,9 +24,8 @@ function getInitialOs(app: IApplication, environment: IEnvironment): OperatingSy
return currentOs;
}
supportedOsList.sort((os1, os2) => {
const os1SupportLevel = app.collections[os1].totalScripts;
const os2SupportLevel = app.collections[os2].totalScripts;
return os1SupportLevel - os2SupportLevel;
const getPriority = (os: OperatingSystem) => app.getCollection(os).totalScripts;
return getPriority(os2) - getPriority(os1);
});
return supportedOsList[0];
}

View File

@@ -1,13 +1,10 @@
import { ICategoryCollectionState } from './State/ICategoryCollectionState';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { ISignal } from '@/infrastructure/Events/ISignal';
import { IApplication } from '@/domain/IApplication';
export interface IApplicationContext {
readonly currentOs: OperatingSystem;
readonly app: IApplication;
readonly collection: ICategoryCollection;
readonly state: ICategoryCollectionState;
readonly contextChanged: ISignal<IApplicationContextChangedEvent>;
changeContext(os: OperatingSystem): void;
@@ -15,6 +12,5 @@ export interface IApplicationContext {
export interface IApplicationContextChangedEvent {
readonly newState: ICategoryCollectionState;
readonly newCollection: ICategoryCollection;
readonly newOs: OperatingSystem;
readonly oldState: ICategoryCollectionState;
}

View File

@@ -6,8 +6,10 @@ import { IUserSelection } from './Selection/IUserSelection';
import { ICategoryCollectionState } from './ICategoryCollectionState';
import { IApplicationCode } from './Code/IApplicationCode';
import { ICategoryCollection } from '../../../domain/ICategoryCollection';
import { OperatingSystem } from '@/domain/OperatingSystem';
export class CategoryCollectionState implements ICategoryCollectionState {
public readonly os: OperatingSystem;
public readonly code: IApplicationCode;
public readonly selection: IUserSelection;
public readonly filter: IUserFilter;
@@ -16,5 +18,6 @@ export class CategoryCollectionState implements ICategoryCollectionState {
this.selection = new UserSelection(collection, []);
this.code = new ApplicationCode(this.selection, collection.scripting);
this.filter = new UserFilter(collection);
this.os = collection.os;
}
}

View File

@@ -3,7 +3,7 @@ import { ICodeBuilder } from './ICodeBuilder';
const NewLine = '\n';
const TotalFunctionSeparatorChars = 58;
export class CodeBuilder implements ICodeBuilder {
export abstract class CodeBuilder implements ICodeBuilder {
private readonly lines = new Array<string>();
// Returns current line starting from 0 (no lines), or 1 (have single line)
@@ -29,7 +29,7 @@ export class CodeBuilder implements ICodeBuilder {
}
public appendCommentLine(commentLine?: string): CodeBuilder {
this.lines.push(`:: ${commentLine}`);
this.lines.push(`${this.getCommentDelimiter()} ${commentLine}`);
return this;
}
@@ -37,9 +37,8 @@ export class CodeBuilder implements ICodeBuilder {
if (!name) { throw new Error('name cannot be empty or null'); }
if (!code) { throw new Error('code cannot be empty or null'); }
return this
.appendLine()
.appendCommentLineWithHyphensAround(name)
.appendLine(`echo --- ${name}`)
.appendLine(this.writeStandardOut(`--- ${name}`))
.appendLine(code)
.appendTrailingHyphensCommentLine();
}
@@ -62,4 +61,7 @@ export class CodeBuilder implements ICodeBuilder {
public toString(): string {
return this.lines.join(NewLine);
}
protected abstract getCommentDelimiter(): string;
protected abstract writeStandardOut(text: string): string;
}

View File

@@ -0,0 +1,15 @@
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { ICodeBuilder } from './ICodeBuilder';
import { ICodeBuilderFactory } from './ICodeBuilderFactory';
import { BatchBuilder } from './Languages/BatchBuilder';
import { ShellBuilder } from './Languages/ShellBuilder';
export class CodeBuilderFactory implements ICodeBuilderFactory {
public create(language: ScriptingLanguage): ICodeBuilder {
switch (language) {
case ScriptingLanguage.shellscript: return new ShellBuilder();
case ScriptingLanguage.batchfile: return new BatchBuilder();
default: throw new RangeError(`unknown language: "${ScriptingLanguage[language]}"`);
}
}
}

View File

@@ -0,0 +1,6 @@
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { ICodeBuilder } from './ICodeBuilder';
export interface ICodeBuilderFactory {
create(language: ScriptingLanguage): ICodeBuilder;
}

View File

@@ -0,0 +1,10 @@
import { CodeBuilder } from '@/application/Context/State/Code/Generation/CodeBuilder';
export class BatchBuilder extends CodeBuilder {
protected getCommentDelimiter(): string {
return '::';
}
protected writeStandardOut(text: string): string {
return `echo ${text}`;
}
}

View File

@@ -0,0 +1,10 @@
import { CodeBuilder } from '@/application/Context/State/Code/Generation/CodeBuilder';
export class ShellBuilder extends CodeBuilder {
protected getCommentDelimiter(): string {
return '#';
}
protected writeStandardOut(text: string): string {
return `echo '${text}'`;
}
}

View File

@@ -1,14 +1,15 @@
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { IUserScriptGenerator } from './IUserScriptGenerator';
import { CodeBuilder } from './CodeBuilder';
import { ICodePosition } from '@/application/Context/State/Code/Position/ICodePosition';
import { CodePosition } from '../Position/CodePosition';
import { IUserScript } from './IUserScript';
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { ICodeBuilder } from './ICodeBuilder';
import { ICodeBuilderFactory } from './ICodeBuilderFactory';
import { CodeBuilderFactory } from './CodeBuilderFactory';
export class UserScriptGenerator implements IUserScriptGenerator {
constructor(private readonly codeBuilderFactory: () => ICodeBuilder = () => new CodeBuilder()) {
constructor(private readonly codeBuilderFactory: ICodeBuilderFactory = new CodeBuilderFactory()) {
}
public buildCode(
@@ -20,7 +21,7 @@ export class UserScriptGenerator implements IUserScriptGenerator {
if (!selectedScripts.length) {
return { code: '', scriptPositions };
}
let builder = this.codeBuilderFactory();
let builder = this.codeBuilderFactory.create(scriptingDefinition.language);
builder = initializeCode(scriptingDefinition.startCode, builder);
for (const selection of selectedScripts) {
scriptPositions = appendSelection(selection, scriptPositions, builder);
@@ -52,16 +53,19 @@ function appendSelection(
selection: SelectedScript,
scriptPositions: Map<SelectedScript, ICodePosition>,
builder: ICodeBuilder): Map<SelectedScript, ICodePosition> {
const startPosition = builder.currentLine + 1;
appendCode(selection, builder);
const startPosition = builder.currentLine + 1; // Because first line will be empty to separate scripts
builder = appendCode(selection, builder);
const endPosition = builder.currentLine - 1;
builder.appendLine();
scriptPositions.set(selection, new CodePosition(startPosition, endPosition));
const position = new CodePosition(startPosition, endPosition);
scriptPositions.set(selection, position);
return scriptPositions;
}
function appendCode(selection: SelectedScript, builder: ICodeBuilder) {
function appendCode(selection: SelectedScript, builder: ICodeBuilder): ICodeBuilder {
const name = selection.revert ? `${selection.script.name} (revert)` : selection.script.name;
const scriptCode = selection.revert ? selection.script.code.revert : selection.script.code.execute;
builder.appendFunction(name, scriptCode);
return builder
.appendLine()
.appendFunction(name, scriptCode);
}

View File

@@ -1,6 +1,6 @@
import { ICodePosition } from './ICodePosition';
export class CodePosition implements ICodePosition {
export class CodePosition implements ICodePosition {
public get totalLines(): number {
return this.endLine - this.startLine;
}

View File

@@ -1,5 +1,5 @@
import { ISignal } from '@/infrastructure/Events/ISignal';
import { IFilterResult } from './IFilterResult';
import { ISignal } from '@/infrastructure/Events/Signal';
export interface IUserFilter {
readonly currentFilter: IFilterResult | undefined;

View File

@@ -1,10 +1,13 @@
import { IUserFilter } from './Filter/IUserFilter';
import { IUserSelection } from './Selection/IUserSelection';
import { IApplicationCode } from './Code/IApplicationCode';
export { IUserSelection, IApplicationCode, IUserFilter };
import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { OperatingSystem } from '@/domain/OperatingSystem';
export interface ICategoryCollectionState {
readonly code: IApplicationCode;
readonly filter: IUserFilter;
readonly selection: IUserSelection;
readonly collection: ICategoryCollection;
readonly os: OperatingSystem;
}

View File

@@ -1,7 +1,7 @@
import { SelectedScript } from './SelectedScript';
import { ISignal } from '@/infrastructure/Events/Signal';
import { IScript } from '@/domain/IScript';
import { ICategory } from '@/domain/ICategory';
import { ISignal } from '@/infrastructure/Events/ISignal';
export interface IUserSelection {
readonly changed: ISignal<ReadonlyArray<SelectedScript>>;

View File

@@ -3,6 +3,7 @@ import { IProjectInformation } from '@/domain/IProjectInformation';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { parseCategoryCollection } from './CategoryCollectionParser';
import WindowsData from 'js-yaml-loader!@/application/collections/windows.yaml';
import MacOsData from 'js-yaml-loader!@/application/collections/macos.yaml';
import { CollectionData } from 'js-yaml-loader!@/*';
import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser';
import { Application } from '@/domain/Application';
@@ -10,10 +11,11 @@ import { Application } from '@/domain/Application';
export function parseApplication(
parser = CategoryCollectionParser,
processEnv: NodeJS.ProcessEnv = process.env,
collectionData = LoadedCollectionData): IApplication {
collectionsData = PreParsedCollections): IApplication {
validateCollectionsData(collectionsData);
const information = parseProjectInformation(processEnv);
const collection = parser(collectionData, information);
const app = new Application(information, [ collection ]);
const collections = collectionsData.map((collection) => parser(collection, information));
const app = new Application(information, collections);
return app;
}
@@ -23,5 +25,14 @@ export type CategoryCollectionParserType
const CategoryCollectionParser: CategoryCollectionParserType
= (file, info) => parseCategoryCollection(file, info);
const LoadedCollectionData: CollectionData
= WindowsData;
const PreParsedCollections: readonly CollectionData []
= [ WindowsData, MacOsData ];
function validateCollectionsData(collections: readonly CollectionData[]) {
if (!collections.length) {
throw new Error('no collection provided');
}
if (collections.some((collection) => !collection)) {
throw new Error('undefined collection provided');
}
}

View File

@@ -1,27 +1,27 @@
import { Category } from '@/domain/Category';
import { CollectionData } from 'js-yaml-loader!@/*';
import { parseCategory } from './CategoryParser';
import { ScriptCompiler } from './Compiler/ScriptCompiler';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { parseScriptingDefinition } from './ScriptingDefinitionParser';
import { createEnumParser } from '../Common/Enum';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { CategoryCollection } from '@/domain/CategoryCollection';
import { IProjectInformation } from '@/domain/IProjectInformation';
import { CategoryCollectionParseContext } from './Script/CategoryCollectionParseContext';
export function parseCategoryCollection(
content: CollectionData,
info: IProjectInformation,
osParser = createEnumParser(OperatingSystem)): ICategoryCollection {
validate(content);
const compiler = new ScriptCompiler(content.functions);
const scripting = parseScriptingDefinition(content.scripting, info);
const context = new CategoryCollectionParseContext(content.functions, scripting);
const categories = new Array<Category>();
for (const action of content.actions) {
const category = parseCategory(action, compiler);
const category = parseCategory(action, context);
categories.push(category);
}
const os = osParser.parseEnum(content.os, 'os');
const scripting = parseScriptingDefinition(content.scripting, info);
const collection = new CategoryCollection(
os,
categories,

View File

@@ -2,8 +2,8 @@ import { CategoryData, ScriptData, CategoryOrScriptData } from 'js-yaml-loader!@
import { Script } from '@/domain/Script';
import { Category } from '@/domain/Category';
import { parseDocUrls } from './DocumentationParser';
import { parseScript } from './ScriptParser';
import { IScriptCompiler } from './Compiler/IScriptCompiler';
import { ICategoryCollectionParseContext } from './Script/ICategoryCollectionParseContext';
import { parseScript } from './Script/ScriptParser';
let categoryIdCounter: number = 0;
@@ -12,17 +12,15 @@ interface ICategoryChildren {
subScripts: Script[];
}
export function parseCategory(category: CategoryData, compiler: IScriptCompiler): Category {
if (!compiler) {
throw new Error('undefined compiler');
}
export function parseCategory(category: CategoryData, context: ICategoryCollectionParseContext): Category {
if (!context) { throw new Error('undefined context'); }
ensureValid(category);
const children: ICategoryChildren = {
subCategories: new Array<Category>(),
subScripts: new Array<Script>(),
};
for (const data of category.children) {
parseCategoryChild(data, children, category, compiler);
parseCategoryChild(data, children, category, context);
}
return new Category(
/*id*/ categoryIdCounter++,
@@ -49,13 +47,13 @@ function parseCategoryChild(
data: CategoryOrScriptData,
children: ICategoryChildren,
parent: CategoryData,
compiler: IScriptCompiler) {
context: ICategoryCollectionParseContext) {
if (isCategory(data)) {
const subCategory = parseCategory(data as CategoryData, compiler);
const subCategory = parseCategory(data as CategoryData, context);
children.subCategories.push(subCategory);
} else if (isScript(data)) {
const scriptData = data as ScriptData;
const script = parseScript(scriptData, compiler);
const script = parseScript(scriptData, context);
children.subScripts.push(script);
} else {
throw new Error(`Child element is neither a category or a script.

View File

@@ -0,0 +1,22 @@
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { ILanguageSyntax } from '@/domain/ScriptCode';
import { FunctionData } from 'js-yaml-loader!*';
import { IScriptCompiler } from './Compiler/IScriptCompiler';
import { ScriptCompiler } from './Compiler/ScriptCompiler';
import { ICategoryCollectionParseContext } from './ICategoryCollectionParseContext';
import { SyntaxFactory } from './Syntax/SyntaxFactory';
import { ISyntaxFactory } from './Syntax/ISyntaxFactory';
export class CategoryCollectionParseContext implements ICategoryCollectionParseContext {
public readonly compiler: IScriptCompiler;
public readonly syntax: ILanguageSyntax;
constructor(
functionsData: ReadonlyArray<FunctionData> | undefined,
scripting: IScriptingDefinition,
syntaxFactory: ISyntaxFactory = new SyntaxFactory()) {
if (!scripting) { throw new Error('undefined scripting'); }
this.syntax = syntaxFactory.create(scripting.language);
this.compiler = new ScriptCompiler(functionsData, this.syntax);
}
}

View File

@@ -3,6 +3,7 @@ import { IScriptCode } from '@/domain/IScriptCode';
import { ScriptCode } from '@/domain/ScriptCode';
import { ScriptData, FunctionData, FunctionCallData, ScriptFunctionCallData, FunctionCallParametersData } from 'js-yaml-loader!@/*';
import { IScriptCompiler } from './IScriptCompiler';
import { ILanguageSyntax } from '@/domain/ScriptCode';
interface ICompiledCode {
readonly code: string;
@@ -10,8 +11,11 @@ interface ICompiledCode {
}
export class ScriptCompiler implements IScriptCompiler {
constructor(private readonly functions: readonly FunctionData[]) {
constructor(
private readonly functions: readonly FunctionData[] | undefined,
private syntax: ILanguageSyntax) {
ensureValidFunctions(functions);
if (!syntax) { throw new Error('undefined syntax'); }
}
public canCompile(script: ScriptData): boolean {
if (!script.call) {
@@ -33,7 +37,7 @@ export class ScriptCompiler implements IScriptCompiler {
compiledCodes.push(functionCode);
});
const scriptCode = merge(compiledCodes);
return new ScriptCode(script.name, scriptCode.code, scriptCode.revertCode);
return new ScriptCode(scriptCode.code, scriptCode.revertCode, script.name, this.syntax);
}
private getFunctionByName(name: string): FunctionData {
@@ -43,7 +47,6 @@ export class ScriptCompiler implements IScriptCompiler {
}
return func;
}
private ensureCompilable(call: ScriptFunctionCallData) {
if (!this.functions || this.functions.length === 0) {
throw new Error('cannot compile without shared functions');
@@ -69,7 +72,11 @@ function ensureNoDuplicatesInFunctionNames(functions: readonly FunctionData[]) {
throw new Error(`duplicate function name: ${printList(duplicateFunctionNames)}`);
}
}
function ensureNoUndefinedItem(functions: readonly FunctionData[]) {
if (functions.some((func) => !func)) {
throw new Error(`some functions are undefined`);
}
}
function ensureNoDuplicatesInParameterNames(functions: readonly FunctionData[]) {
const functionsWithParameters = functions
.filter((func) => func.parameters && func.parameters.length > 0);
@@ -95,9 +102,10 @@ function ensureNoDuplicateCode(functions: readonly FunctionData[]) {
}
function ensureValidFunctions(functions: readonly FunctionData[]) {
if (!functions) {
if (!functions || functions.length === 0) {
return;
}
ensureNoUndefinedItem(functions);
ensureNoDuplicatesInFunctionNames(functions);
ensureNoDuplicatesInParameterNames(functions);
ensureNoDuplicateCode(functions);

View File

@@ -0,0 +1,7 @@
import { ILanguageSyntax } from '@/domain/ScriptCode';
import { IScriptCompiler } from './Compiler/IScriptCompiler';
export interface ICategoryCollectionParseContext {
readonly compiler: IScriptCompiler;
readonly syntax: ILanguageSyntax;
}

View File

@@ -1,22 +1,20 @@
import { Script } from '@/domain/Script';
import { ScriptData } from 'js-yaml-loader!@/*';
import { parseDocUrls } from './DocumentationParser';
import { parseDocUrls } from '../DocumentationParser';
import { RecommendationLevel } from '@/domain/RecommendationLevel';
import { IScriptCompiler } from './Compiler/IScriptCompiler';
import { IScriptCode } from '@/domain/IScriptCode';
import { ScriptCode } from '@/domain/ScriptCode';
import { createEnumParser, IEnumParser } from '../Common/Enum';
import { createEnumParser, IEnumParser } from '../../Common/Enum';
import { ICategoryCollectionParseContext } from './ICategoryCollectionParseContext';
export function parseScript(
data: ScriptData, compiler: IScriptCompiler,
data: ScriptData, context: ICategoryCollectionParseContext,
levelParser = createEnumParser(RecommendationLevel)): Script {
validateScript(data);
if (!compiler) {
throw new Error('undefined compiler');
}
if (!context) { throw new Error('undefined context'); }
const script = new Script(
/* name */ data.name,
/* code */ parseCode(data, compiler),
/* code */ parseCode(data, context),
/* docs */ parseDocUrls(data),
/* level */ parseLevel(data.recommend, levelParser));
return script;
@@ -29,11 +27,11 @@ function parseLevel(level: string, parser: IEnumParser<RecommendationLevel>): Re
return parser.parseEnum(level, 'level');
}
function parseCode(script: ScriptData, compiler: IScriptCompiler): IScriptCode {
if (compiler.canCompile(script)) {
return compiler.compile(script);
function parseCode(script: ScriptData, context: ICategoryCollectionParseContext): IScriptCode {
if (context.compiler.canCompile(script)) {
return context.compiler.compile(script);
}
return new ScriptCode(script.name, script.code, script.revertCode);
return new ScriptCode(script.code, script.revertCode, script.name, context.syntax);
}
function ensureNotBothCallAndCode(script: ScriptData) {

View File

@@ -0,0 +1,6 @@
import { ILanguageSyntax } from '@/domain/ScriptCode';
export class BatchFileSyntax implements ILanguageSyntax {
public readonly commentDelimiters = [ 'REM', '::' ];
public readonly commonCodeParts = [ '(', ')', 'else' ];
}

View File

@@ -0,0 +1,6 @@
import { ILanguageSyntax } from '@/domain/ScriptCode';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
export interface ISyntaxFactory {
create(language: ScriptingLanguage): ILanguageSyntax;
}

View File

@@ -0,0 +1,6 @@
import { ILanguageSyntax } from '@/domain/ScriptCode';
export class ShellScriptSyntax implements ILanguageSyntax {
public readonly commentDelimiters = [ '#' ];
public readonly commonCodeParts = [ '(', ')', 'else' ];
}

View File

@@ -0,0 +1,15 @@
import { ILanguageSyntax } from '@/domain/ScriptCode';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { ISyntaxFactory } from './ISyntaxFactory';
import { BatchFileSyntax } from './BatchFileSyntax';
import { ShellScriptSyntax } from './ShellScriptSyntax';
export class SyntaxFactory implements ISyntaxFactory {
public create(language: ScriptingLanguage): ILanguageSyntax {
switch (language) {
case ScriptingLanguage.batchfile: return new BatchFileSyntax();
case ScriptingLanguage.shellscript: return new ShellScriptSyntax();
default: throw new RangeError(`unknown language: "${ScriptingLanguage[language]}"`);
}
}
}

View File

@@ -4,7 +4,7 @@ import { ScriptingDefinition } from '@/domain/ScriptingDefinition';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { IProjectInformation } from '@/domain/IProjectInformation';
import { createEnumParser } from '../Common/Enum';
import { generateIlCode } from './Compiler/ILCode';
import { generateIlCode } from './Script/Compiler/ILCode';
export function parseScriptingDefinition(
definition: ScriptingDefinitionData,

View File

@@ -38,10 +38,10 @@ declare module 'js-yaml-loader!*' {
export interface ScriptData extends DocumentableData {
name: string;
code: string | undefined;
revertCode: string | undefined;
code?: string;
revertCode?: string;
call: ScriptFunctionCallData;
recommend: string | undefined;
recommend?: string;
}
export interface ScriptingDefinitionData {

View File

@@ -0,0 +1,225 @@
# Structure documented in "docs/collections.md"
os: macos
scripting:
language: shellscript
startCode: |-
#!/usr/bin/env bash
# {{ $homepage }} — v{{ $version }} — {{ $date }}
if [ "$EUID" -ne 0 ]; then
script_path=$([[ "$0" = /* ]] && echo "$0" || echo "$PWD/${0#./}")
sudo "$script_path" || (
echo 'Administrator privileges are required.'
exit 1
)
exit 0
fi
endCode: |-
echo 'Your privacy and security is now hardened 🎉💪'
echo 'Press any key to exit.'
read -n 1 -s
actions:
-
category: Privacy cleanup
children:
-
category: Clear terminal history
children:
-
name: Clear bash history
recommend: standard
code: rm -f ~/.bash_history
-
name: Clear zsh history
recommend: standard
code: rm -f ~/.zsh_history
-
name: Clear CUPS printer job cache
recommend: strict
code: |-
sudo rm -rfv /var/spool/cups/c0*
sudo rm -rfv /var/spool/cups/tmp/*
sudo rm -rfv /var/spool/cups/cache/job.cache*
-
name: Clear the list of iOS devices connected
recommend: strict
code: |-
sudo defaults delete /Users/$USER/Library/Preferences/com.apple.iPod.plist "conn:128:Last Connect"
sudo defaults delete /Users/$USER/Library/Preferences/com.apple.iPod.plist Devices
sudo defaults delete /Library/Preferences/com.apple.iPod.plist "conn:128:Last Connect"
sudo defaults delete /Library/Preferences/com.apple.iPod.plist Devices
sudo rm -rfv /var/db/lockdown/*
-
name: Reset privacy database (remove all permissions)
code: sudo tccutil reset All
-
category: Configure programs
children:
-
name: Disable Firefox telemetry
recommend: standard
docs: https://github.com/mozilla/policy-templates/blob/master/README.md
code: |-
# Enable Firefox policies so the telemetry can be configured.
sudo defaults write /Library/Preferences/org.mozilla.firefox EnterprisePoliciesEnabled -bool TRUE
# Disable sending usage data
sudo defaults write /Library/Preferences/org.mozilla.firefox DisableTelemetry -bool TRUE
revertCode: |-
sudo defaults delete /Library/Preferences/org.mozilla.firefox EnterprisePoliciesEnabled
sudo defaults delete /Library/Preferences/org.mozilla.firefox DisableTelemetry
-
name: Disable Microsoft Office diagnostics data sending
recommend: standard
code: defaults write com.microsoft.office DiagnosticDataTypePreference -string ZeroDiagnosticData
revertCode: defaults delete com.microsoft.office DiagnosticDataTypePreference
-
name: Uninstall Google update
recommend: strict
code: |-
googleUpdateFile=~/Library/Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/Contents/Resources/ksinstall
if [ -f "$googleUpdateFile" ]; then
$googleUpdateFile --nuke
echo Uninstalled google update
else
echo Google update file does not exist
fi
-
name: Disable Homebrew user behavior analytics
recommend: standard
docs: https://docs.brew.sh/Analytics
call:
-
function: PersistUserEnvironmentConfiguration
parameters:
configuration: export HOMEBREW_NO_ANALYTICS=1
-
name: Disable NET Core CLI telemetry
recommend: standard
call:
-
function: PersistUserEnvironmentConfiguration
parameters:
configuration: export DOTNET_CLI_TELEMETRY_OPTOUT=1
-
name: Disable PowerShell Core telemetry
recommend: standard
docs: https://github.com/PowerShell/PowerShell/tree/release/v7.1.1#telemetry
call:
-
function: PersistUserEnvironmentConfiguration
parameters:
configuration: export POWERSHELL_TELEMETRY_OPTOUT=1
-
category: Configure OS
children:
-
category: Configure Apple Remote Desktop
children:
-
name: Deactivate the Remote Management Service
recommend: strict
code: sudo /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -deactivate -stop
revertCode: sudo /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -activate -restart -agent -console
-
name: Remove Apple Remote Desktop Settings
recommend: strict
code: |-
sudo rm -rf /var/db/RemoteManagement
sudo defaults delete /Library/Preferences/com.apple.RemoteDesktop.plist
defaults delete ~/Library/Preferences/com.apple.RemoteDesktop.plist
sudo rm -r /Library/Application\ Support/Apple/Remote\ Desktop/
rm -r ~/Library/Application\ Support/Remote\ Desktop/
rm -r ~/Library/Containers/com.apple.RemoteDesktop
-
name: Disable Internet based spell correction
code: defaults write NSGlobalDomain WebAutomaticSpellingCorrectionEnabled -bool false
revertCode: defaults delete NSGlobalDomain WebAutomaticSpellingCorrectionEnabled
-
name: Disable Remote Apple Events
recommend: strict
code: sudo systemsetup -setremoteappleevents off
revertCode: sudo systemsetup -setremoteappleevents on
-
name: Do not store documents to iCloud Drive by default
docs: https://macos-defaults.com/finder/nsdocumentsavenewdocumentstocloud.html
recommend: standard
code: defaults write NSGlobalDomain NSDocumentSaveNewDocumentsToCloud -bool false
revertCode: defaults delete NSGlobalDomain NSDocumentSaveNewDocumentsToCloud
-
category: Security improvements
children:
-
category: Configure macOS Application Firewall
children:
-
name: Enable firewall
recommend: standard
docs: https://www.stigviewer.com/stig/apple_os_x_10.13/2018-10-01/finding/V-81681
code: /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate on
revertCode: /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate off
-
name: Turn on firewall logging
recommend: standard
docs: https://www.stigviewer.com/stig/apple_os_x_10.13/2018-10-01/finding/V-81671
code: /usr/libexec/ApplicationFirewall/socketfilterfw --setloggingmode on
revertCode: /usr/libexec/ApplicationFirewall/socketfilterfw --setloggingmode off
-
name: Turn on stealth mode
recommend: standard
docs: https://www.stigviewer.com/stig/apple_os_x_10.8_mountain_lion_workstation/2015-02-10/finding/V-51327
code: /usr/libexec/ApplicationFirewall/socketfilterfw --setstealthmode on
revertCode: /usr/libexec/ApplicationFirewall/socketfilterfw --setstealthmode off
-
name: Disable Spotlight indexing
code: sudo mdutil -i off -d /
revertCode: sudo mdutil -i on /
-
name: Disable Captive portal
docs:
- https://web.archive.org/web/20171008071031if_/http://blog.erratasec.com/2010/09/apples-secret-wispr-request.html#.WdnPa5OyL6Y
- https://web.archive.org/web/20130407200745/http://www.divertednetworks.net/apple-captiveportal.html
- https://web.archive.org/web/20170622064304/https://grpugh.wordpress.com/2014/10/29/an-undocumented-change-to-captive-network-assistant-settings-in-os-x-10-10-yosemite/
code: sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.captive.control.plist Active -bool false
revertCode: sudo defaults delete /Library/Preferences/SystemConfiguration/com.apple.captive.control.plist Active
-
name: Require a password to wake the computer from sleep or screen saver
code: defaults write /Library/Preferences/com.apple.screensaver askForPassword -bool true
revertCode: sudo defaults delete /Library/Preferences/com.apple.screensaver askForPassword
-
name: Do not show recent items on dock
docs: https://developer.apple.com/documentation/devicemanagement/dock
code: defaults write com.apple.dock show-recents -bool false
revertCode: defaults delete com.apple.dock show-recents
-
name: Disable AirDrop file sharing
recommend: strict
code: defaults write com.apple.NetworkBrowser DisableAirDrop -bool true
revertCode: defaults write com.apple.NetworkBrowser DisableAirDrop -bool false
functions:
-
name: PersistUserEnvironmentConfiguration
parameters: [ configuration ]
code: |-
command='{{ $configuration }}'
declare -a profile_files=("$HOME/.bash_profile" "$HOME/.zprofile")
for profile_file in "${profile_files[@]}"
do
touch "$profile_file"
if ! grep -q "$command" "${profile_file}"; then
echo "$command" >> "$profile_file"
echo "[$profile_file] Configured"
else
echo "[$profile_file] No need for any action, already configured"
fi
done
revertCode: |-
command='{{ $configuration }}'
declare -a profile_files=("$HOME/.bash_profile" "$HOME/.zprofile")
for profile_file in "${profile_files[@]}"
do
if grep -q "$command" "${profile_file}" 2>/dev/null; then
sed -i '' "/$command/d" "$profile_file"
echo "[$profile_file] Reverted configuration"
else
echo "[$profile_file] No need for any action, configuration does not exist"
fi
done