Compare commits

..

143 Commits
0.4.0 ... 0.7.6

Author SHA1 Message Date
undergroundwires
d9d7f62d81 run tests on all operating systems: macos, ubuntu, windows 2020-10-18 02:10:20 +01:00
undergroundwires
11e0613165 update dependencies to latest 2020-10-17 23:17:44 +01:00
undergroundwires
77c3d2bbb8 simplify "why" section 2020-09-23 20:42:05 +01:00
undergroundwires
784a67afff refactor to read more from package.json 2020-09-22 20:41:12 +01:00
undergroundwires
19a092dd31 add more reversibility 2020-09-21 23:05:31 +01:00
undergroundwires
4c2f74949b add robots.txt to explicitly allow indexing 2020-09-19 01:23:33 +01:00
undergroundwires
a3fc3782ef add docs for default0 pointing to github discussion (#30) 2020-09-20 13:58:19 +01:00
undergroundwires-bot
cdc93f032a ⬆️ bumped to 0.7.5 2020-09-19 13:41:58 +00:00
undergroundwires
7dd15ed064 fix typo 2020-09-19 15:39:48 +01:00
undergroundwires
d169434157 fix pasting in search bar after page load showing no results 2020-09-18 20:07:03 +01:00
undergroundwires
6efed72bf2 fix rendering issue in older edge/IE 2020-09-17 15:41:46 +01:00
Clayton Errington
15db311801 fix the recycling bin option (#32)
* update the recycling bin option

Powershell has a module in PS 5.1+ called Clear-Recyclebin that works better than the CMD method - rd /s %systemdrive%\$Recycle.bin

* update recycling bin delete command

one liner of ComObject in powershell instead of cmd asking for confirmation. adds better backwards compatibility.
2020-09-16 20:22:33 +00:00
undergroundwires
82d509129b fix tests and checks are not running on PRs 2020-09-16 19:10:25 +01:00
undergroundwires
939d838e35 fix reverting (reinstalling) capabilities not working 2020-09-16 02:09:23 +01:00
undergroundwires-bot
6de4ce58c4 ⬆️ bumped to 0.7.4 2020-09-14 13:57:39 +00:00
undergroundwires
ee66196d9a fix wrong path in clear all firefox user profile settings 2020-09-14 16:04:38 +01:00
undergroundwires
3c13a9e837 fix missing reg value in denying app access to account 2020-09-14 16:03:03 +01:00
undergroundwires
22b23a9ece fix spectre protection getting single lined #31 2020-09-14 16:00:20 +01:00
undergroundwires
4ae385b7fc fix checked checkbox has blue border 2020-09-13 18:42:19 +01:00
undergroundwires-bot
d9abc7f0b2 ⬆️ bumped to 0.7.3 2020-09-12 13:14:13 +00:00
undergroundwires
1f19b2528a fix typo in a test 2020-09-12 14:42:27 +01:00
undergroundwires
1f11c39773 add more detailed error message 2020-09-12 00:14:27 +01:00
undergroundwires
b6ccb5927a fix comment lines are being detected as duplicate in validation 2020-09-12 00:13:58 +01:00
undergroundwires
1d465ee318 add reversibility and more scripts to denying app access with better structure 2020-09-12 00:11:10 +01:00
undergroundwires
3ab48b1cf5 fix naming of firefox cleanup to mention profiles 2020-09-11 14:26:32 +01:00
undergroundwires
de4ac978bd fix wrong path to the main telemetry file 2020-09-10 12:52:29 +01:00
undergroundwires
8df5faf4ef improve CPU specific tweaks by conditional platform checks and reversibility 2020-09-09 13:55:21 +01:00
undergroundwires
99a2035fdb fix nvidia tweak error message, categorize and add reversibility 2020-09-08 19:41:03 +01:00
undergroundwires
a0d61728ea fix vscode settings file override and add more configs 2020-09-07 13:42:59 +01:00
undergroundwires-bot
312bf6102c ⬆️ bumped to 0.7.2 2020-09-06 18:10:12 +00:00
undergroundwires
f4885b6f1c add best practice suggestion to come back 2020-09-06 02:41:11 +01:00
undergroundwires
ca63a0979e fix wording in default text in text area 2020-09-06 02:37:47 +01:00
undergroundwires
1f266c3353 fix indeterminate state being lost 2020-09-06 15:26:19 +01:00
undergroundwires
c7b2a70312 add reversibility to removing bloatware 2020-09-06 19:03:36 +01:00
undergroundwires
255133af4d fix bad highlighting of selected nodes when using keyboard navigation 2020-09-04 01:24:35 +01:00
undergroundwires
db74531cd4 add reversibility for biometric disabling and do not recommend it 2020-09-06 16:39:48 +01:00
undergroundwires
f36d8bfc78 update onesync documentation and do not recommend it as it breaks other apps 2020-09-05 23:31:31 +01:00
undergroundwires-bot
3b31ace726 ⬆️ bumped to 0.7.1 2020-09-04 11:42:03 +00:00
undergroundwires
6badfef9da refactor unused imports 2020-09-04 13:29:42 +01:00
undergroundwires
8c38dd73d8 fix new/changed script higlighting not working on production builds 2020-09-04 13:26:35 +01:00
undergroundwires
b8682a852a rename screenshot image file 2020-09-04 13:25:36 +01:00
undergroundwires
8c17929151 fix some browsers (including firefox) downloading the script as a text file 2020-09-04 12:20:41 +01:00
undergroundwires-bot
bb92c9ec28 ⬆️ bumped to 0.7.0 2020-09-02 21:26:40 +00:00
undergroundwires
b4aacea2a3 update the screenshot to show off highlighting 2020-09-02 19:12:24 +01:00
undergroundwires
8bbe6ebf75 fix search (got broken in b789250) with tests and refactorings 2020-09-02 22:44:20 +01:00
undergroundwires
a23d28f2cf refactor unused imports & variables 2020-09-01 21:32:31 +01:00
undergroundwires
f51e8859ee add reversibility on category level 2020-09-01 21:18:16 +01:00
undergroundwires
d235dee955 exclude paint, wordpad and notepad from bloatware removal 2020-09-01 21:03:14 +01:00
undergroundwires
2afef4ea3d do not hardcode capability versions and make them reversible 2020-08-26 00:58:52 +01:00
undergroundwires
f709d6a566 fix "Configure Defender" being in wrong category #28 2020-09-01 20:06:44 +01:00
undergroundwires
532915b95d categorize, fix, make scripts reversible in "UI for privacy", "security improvements" and "configure browsers" 2020-09-01 20:03:43 +01:00
Francesco Saltori
456e40bedf Add disabling of PowerShell 7+ telemetry (#29) 2020-09-01 19:57:43 +02:00
undergroundwires
018b7e270f add disabling ccleaner telemetry 2020-08-20 00:29:35 +01:00
undergroundwires
f8b8b4c97a add disabling firefox telemetry 2020-08-30 17:42:05 +01:00
undergroundwires
978d7d0863 add more OneDrive cleanup scripts and categorize them 2020-08-30 16:05:02 +01:00
undergroundwires
594a14d6ca categorize, fix and extend windows log files cleanup 2020-08-24 21:21:21 +01:00
undergroundwires
c628aa9aef updated dependencies to latest and audit fixes (#25) 2020-08-28 16:46:09 +01:00
undergroundwires
3060ebf79c fix NTP script documentation is on wrong place 2020-08-28 14:16:11 +01:00
undergroundwires
1a34c7374b add more windows defender tweaks, categorization and reversibility 2020-08-27 20:32:19 +01:00
undergroundwires
c262681011 add removal of ghost (default0) telemetry user 2020-08-26 21:31:12 +01:00
undergroundwires
f8ba5c46e4 prompt admin priviliges automatically 2020-08-26 01:58:29 +01:00
undergroundwires
b789250cb8 add auto-highlighting of selected/updated code 2020-08-25 16:52:38 +01:00
undergroundwires
5df458739d move script generation to /generation 2020-08-25 16:44:47 +01:00
undergroundwires
d6fa9a2a03 [search] added clear/close button 2020-08-24 02:50:47 +01:00
undergroundwires
ec15af01dd [search] better (multilined) message when there are no results 2020-08-24 02:50:47 +01:00
undergroundwires-bot
7073336f81 ⬆️ bumped to 0.6.2 2020-08-16 16:16:50 +00:00
undergroundwires
3d3380f27e 🔥 removed disabling ClickToRun as it breaks office 2020-08-16 16:58:05 +02:00
undergroundwires
c69998c7cb 🐛 fixed changing time server not working 2020-08-16 16:48:37 +01:00
undergroundwires
1663bfeac7 added script to clear dotnet telemery 2020-08-16 14:19:44 +02:00
undergroundwires
afc3bfb3b8 ⚙️ enhanced tweak to disable for office telemetry 2020-08-16 14:18:20 +02:00
undergroundwires
b6bfc25727 🐛 fixed removing onedrive does not delete scheduled tasks 2020-08-10 19:12:04 +02:00
undergrounwdires
7fac0fe79f 🐛 fixed blank screen and icons on mac 2020-08-10 18:40:16 +01:00
undergrounwdires
5967347b80 🐛 fixed disabling error reporting for november 2019 update 2020-08-09 16:23:28 +02:00
undergroundwires-bot
855a445c1a ⬆️ bumped to 0.6.1 2020-08-09 01:03:43 +00:00
undergroundwires
1cc12195a3 fixed removing onedrive does not clean start menu / quick access 2020-08-09 03:00:19 +01:00
undergroundwires
66d4d39d5b refactorings 2020-08-09 03:00:18 +01:00
undergroundwires
a5dbe66fc1 tweaks to disable webcam, speech and compatibility telemetry 2020-08-09 03:00:18 +01:00
undergroundwires
4c8be45e28 fixed mac / linux download links 2020-08-09 03:00:18 +01:00
undergroundwires
6049a2b834 moved windows connect now to security & recommended 2020-08-09 03:00:18 +01:00
undergroundwires
831c014f97 more scripts can be reverted 2020-08-09 03:00:18 +01:00
undergroundwires
5c15a7a64a fixed typo in footer 2020-08-09 03:00:18 +01:00
dependabot[bot]
e43992b278 Bump elliptic from 6.5.2 to 6.5.3 (#23)
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.2 to 6.5.3.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.2...v6.5.3)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-08-09 03:00:18 +01:00
undergroundwires
5963d2bac5 updated documentation 2020-08-09 03:00:18 +01:00
undergroundwires
45816a2bcc updated dependencies to latest 2020-08-09 03:00:17 +01:00
undergroundwires
60a5a2aa40 reworked on footer & removed github icon 2020-08-09 03:00:17 +01:00
undergroundwires
04b9b59e14 support for desktop versions #20 2020-08-09 03:00:13 +01:00
undergroundwires
4ff4b52202 code area now shows "how" before "why" 2020-07-24 15:43:19 +01:00
undergroundwires
73c426844a runs tests on each push on the repository 2020-07-19 15:14:23 +01:00
undergroundwires
25ce236a77 fixed dead links in documentation 2020-07-19 02:37:54 +01:00
undergroundwires-bot
9b20175545 ⬆️ bumped to 0.5.0 2020-07-19 00:32:13 +00:00
undergroundwires
92a7118d1c patched loadash vulnerability (#18) 2020-07-19 02:27:01 +01:00
undergroundwires
a9f9e90443 all cards in same line now have same height 2020-07-19 02:27:01 +01:00
undergroundwires
31d2067f07 opening a card scrolls to its content div 2020-07-19 02:27:01 +01:00
undergroundwires
dd7e1416b4 do not collapse card when on "Search" and "Select" 2020-07-19 02:27:01 +01:00
undergroundwires
1d5225de07 search placeholder shows total scripts 2020-07-19 02:27:01 +01:00
undergroundwires
9c063d59de added ability to revert (#21) 2020-07-19 02:26:56 +01:00
undergroundwires-bot
57028987f1 ⬆️ bumped to 0.4.10 2020-07-15 16:52:43 +01:00
undergroundwires
9e722ddfb3 fixed script errors & added tests 2020-07-15 16:52:18 +01:00
undergroundwires-bot
646a8e0b9f ⬆️ bumped to 0.4.9 2020-07-14 21:38:19 +00:00
undergroundwires
f27a2871d7 simplified docker builds 2020-07-14 18:20:15 +01:00
undergroundwires
909c44d72a updated to may 2020 update 2020-07-14 18:20:15 +01:00
undergroundwires
53cf595e17 disable office telemetry Disassembler0/Win10-Initial-Setup-Script#288 2020-07-14 18:20:15 +01:00
undergroundwires
2c4eb78c3f more tweaks #16 2020-07-11 17:05:21 +01:00
Disk2019
d7a1325c0b updated one more typo (#19) 2020-07-11 00:34:59 +01:00
undergroundwires
30efbcc621 can disable features, capabilities & remove onedrive #16 2020-07-11 00:23:23 +01:00
undergroundwires
628c16eb95 stopping services before disabling #16 2020-07-11 00:23:23 +01:00
undergroundwires
d8552c62ff added more scripts #16 (#17) 2020-07-11 00:23:13 +01:00
undergroundwires-bot
df84083536 ⬆️ bumped to 0.4.7 2020-06-29 20:29:16 +00:00
Disk2019
461a4f122b Fixed types + script in "Clear Windows log files" (#15)
Kindly cross check & approve
2020-06-29 20:28:14 +00:00
undergroundwires
c937af8ee7 removed HKU tweak as all HKU's are changed #10 2020-06-29 16:20:35 +01:00
undergroundwires-bot
636d4279c8 ⬆️ bumped to 0.4.6 2020-06-29 14:15:03 +00:00
Disk2019
019b838925 Updated Some More Tweaks (#13) 2020-06-29 16:06:22 +01:00
Disk2019
0fc18459cd Updated Some Tweaks (#11)
ResetBase is by Default Disabled in Win10 Dism.exe /online /Cleanup-Image /StartComponentCleanup . Hence added this tweak to enable it in script on line 271 .
Updated HKU Tweaks to Correct HKCU Values & removed last experimental tweak as its not needed anymore & does not do anything exccept loading default user hive & then unloading it.
2020-06-29 15:51:40 +01:00
undergroundwires
583c5660d6 removed failing continuous deployment #14 2020-06-29 14:51:52 +01:00
Disk2019
52d5713a99 Fixed Some More Issues (#12)
Fixed typo issues :
Force enable data execution prevention (DEP)
disable cortana
Disable diagnostics telemetry
Empty trash bin
2020-06-19 20:08:16 +00:00
undergroundwires-bot
b34a66f270 ⬆️ bumped to 0.4.5 2020-06-13 00:59:26 +00:00
dependabot[bot]
eed996f608 Bump websocket-extensions from 0.1.3 to 0.1.4 (#9)
Bumps [websocket-extensions](https://github.com/faye/websocket-extensions-node) from 0.1.3 to 0.1.4.
- [Release notes](https://github.com/faye/websocket-extensions-node/releases)
- [Changelog](https://github.com/faye/websocket-extensions-node/blob/master/CHANGELOG.md)
- [Commits](https://github.com/faye/websocket-extensions-node/compare/0.1.3...0.1.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-06-13 00:59:03 +00:00
undergroundwires-bot
b96c5d0557 ⬆️ bumped to 0.4.4 2020-05-24 19:25:47 +01:00
undergroundwires
aab8f21a8d clicking outside of a card closes it 2020-05-24 19:25:47 +01:00
undergroundwires
c668a97950 fix "group by" overflows on smaller screens 2020-05-24 19:25:47 +01:00
undergroundwires
bb98d20637 one command to lint everything "npm run lint" 2020-05-24 19:25:47 +01:00
undergroundwires
e2ab124fb7 new footer with privacy policy 2020-05-24 19:25:47 +01:00
undergroundwires
0d2efe5b05 fixed close card button not being visible & cleanup 2020-05-24 19:25:47 +01:00
undergroundwires-bot
156a6554ef ⬆️ bumped to 0.4.3 2020-05-24 19:25:47 +01:00
undergroundwires
4a91e8ccd8 automated using bump-everywhere + more quality checks (#8)
- new workflows
- linting commands & linted stuff
- security checks & fixed audited vulnerabilities
- updated documentation
2020-05-24 19:25:43 +01:00
undergroundwires
997be7113f using deployment operations from aws-static-site-with-cd 2020-05-23 22:59:29 +01:00
undergroundwires
3e3bc07576 automatically increases patch number #5 2020-04-26 15:47:20 +00:00
undergroundwires
691f989682 reading version from package.json instead of version file #5 2020-04-26 15:47:20 +00:00
undergroundwires
226074c534 simplified heading 2020-04-26 15:47:20 +00:00
undergroundwires
97b7e03233 fixed broke link 2020-04-26 15:47:20 +00:00
undergroundwires
749a140eb8 removed redundant documentation 2020-04-26 15:47:20 +00:00
undergroundwires
4739a4ac40 Merge pull request #4 from undergroundwires/dependabot/npm_and_yarn/acorn-6.4.1
Bump acorn from 6.4.0 to 6.4.1
2020-04-05 16:58:59 +00:00
dependabot[bot]
4800340b9b Bump acorn from 6.4.0 to 6.4.1
Bumps [acorn](https://github.com/acornjs/acorn) from 6.4.0 to 6.4.1.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/6.4.0...6.4.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-04-05 16:20:27 +00:00
undergroundwires
074734242b 🚀 0.4.2 release 2020-02-29 18:51:55 +01:00
undergroundwires
802b36bdd8 shortened all HKEY paths 2020-01-12 10:10:37 +01:00
undergroundwires
0c39a06be5 set font on input 2020-01-11 10:55:43 +01:00
undergroundwires
e63ac4ae67 added missing semicolon for masking 2020-01-11 09:45:37 +01:00
undergroundwires
edd076fade 🚀 0.4.1 release 2020-01-11 09:27:19 +01:00
undergroundwires
0ce354ea09 using right 🔍 input type 2020-01-11 09:14:45 +01:00
undergroundwires
19813b6917 more efficient queries with single lowercase 2020-01-11 08:57:24 +01:00
undergroundwires
97a7747933 👀🔍 showing search queries 2020-01-11 08:57:06 +01:00
undergroundwires
92f1a36bcb hide grouping while searching 2020-01-11 07:20:44 +01:00
undergroundwires
31364bdfec fixed search bug 2020-01-11 07:18:02 +01:00
176 changed files with 15593 additions and 4876 deletions

9
.dockerignore Normal file
View File

@@ -0,0 +1,9 @@
node_modules
dist
dist_electron
.vs
.vscode
.github
.git
docs
docker

View File

@@ -1,91 +0,0 @@
name: Build & deploy
on:
push:
branches:
- master
jobs:
build-and-deploy:
runs-on: ubuntu-18.04
steps:
-
name: "Prepare: Checkout"
uses: actions/checkout@v1
-
name: "Prepare: Create AWS user profile"
run: >-
bash "aws/scripts/configure/create-user-profile.sh" \
--profile user \
--access-key-id ${{secrets.AWS_DEPLOYMENT_USER_ACCESS_KEY_ID}} \
--secret-access-key ${{secrets.AWS_DEPLOYMENT_USER_SECRET_ACCESS_KEY}} \
--region us-east-1
-
name: "Infrastructure: Deploy IAM stack"
run: >-
bash "aws/scripts/deploy/deploy-stack.sh" \
--template-file aws/iam-stack.yaml \
--stack-name privacysexy-iam-stack \
--capabilities CAPABILITY_IAM \
--region us-east-1 --role-arn ${{secrets.AWS_IAM_STACK_DEPLOYMENT_ROLE_ARN}} \
--profile user --session ${{github.actor}}-${{github.event_name}}-${{github.sha}}
-
name: "Infrastructure: Deploy certificate stack"
run: >-
bash "aws/scripts/deploy/deploy-stack.sh" \
--template-file aws/certificate-stack.yaml \
--stack-name privacysexy-certificate-stack \
--region us-east-1 \
--role-arn ${{secrets.AWS_CERTIFICATE_STACK_DEPLOYMENT_ROLE_ARN}} \
--profile user --session ${{github.actor}}-${{github.event_name}}-${{github.sha}}
-
name: "Infrastructure: Deploy DNS stack"
run: >-
bash "aws/scripts/deploy/deploy-stack.sh" \
--template-file aws/dns-stack.yaml \
--stack-name privacysexy-dns-stack \
--region us-east-1 \
--role-arn ${{secrets.AWS_DNS_STACK_DEPLOYMENT_ROLE_ARN}} \
--profile user --session ${{github.actor}}-${{github.event_name}}-${{github.sha}}
-
name: "Infrastructure: Deploy web stack"
run: >-
bash "aws/scripts/deploy/deploy-stack.sh" \
--template-file aws/web-stack.yaml \
--stack-name privacysexy-web-stack \
--region us-east-1 \
--role-arn ${{secrets.AWS_WEB_STACK_DEPLOYMENT_ROLE_ARN}} \
--profile user --session ${{github.actor}}-${{github.event_name}}-${{github.sha}}
-
name: "App: Setup node"
uses: actions/setup-node@v1
with:
node-version: '11.x'
-
name: "App: Install dependencies"
run: npm install
-
name: "App: Run tests"
run: npm run test:unit
-
name: "App: Build"
run: npm run build
-
name: "App: Deploy to S3"
run: >-
bash "aws/scripts/deploy/deploy-to-s3.sh" \
--folder dist \
--web-stack-name privacysexy-web-stack --web-stack-s3-name-output-name S3BucketName \
--storage-class ONEZONE_IA \
--role-arn ${{secrets.AWS_S3_SITE_DEPLOYMENT_ROLE_ARN}} \
--region us-east-1 \
--profile user --session ${{github.actor}}-${{github.event_name}}-${{github.sha}}
-
name: "App: Invalidate CloudFront cache"
run: >-
bash "aws/scripts/deploy/invalidate-cloudfront-cache.sh" \
--paths "/*" \
--web-stack-name privacysexy-web-stack --web-stack-cloudfront-arn-output-name CloudFrontDistributionArn \
--role-arn ${{secrets.AWS_CLOUDFRONT_SITE_DEPLOYMENT_ROLE_ARN}} \
--region us-east-1 \
--profile user --session ${{github.actor}}-${{github.event_name}}-${{github.sha}}

17
.github/workflows/bump-and-release.yaml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: Bump & release
on:
push: # Ensure a new release is created for each new tag
tags:
- '[0-9]+.[0-9]+.[0-9]+'
jobs:
bump-version-and-release:
if: github.event.base_ref == 'refs/heads/master'
runs-on: ubuntu-latest
steps:
- uses: undergroundwires/bump-everywhere@master
with:
user: undergroundwires-bot
release-token: ${{ secrets.BUMP_GITHUB_PAT }} # Does not trigger release pipeline if we use default token: https://github.community/t5/GitHub-Actions/Github-Action-trigger-on-release-not-working-if-releases-was/td-p/34559
# GitHub does not inject secrets if pipeline runs from fork or a fork is merged to main repo.

32
.github/workflows/deploy-desktop.yaml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: Deploy desktop
on:
release:
types: [created] # will be triggered when a NON-draft release is created and published.
jobs:
publish-desktop-app:
name: ${{ matrix.os }}
strategy:
matrix:
os: [macos, ubuntu, windows]
runs-on: ${{ matrix.os }}-latest
steps:
- uses: actions/checkout@v2
with:
ref: master # otherwise it defaults to the version tag missing bump commit
fetch-depth: 0 # fetch all history
- name: Checkout to bump commit
run: git checkout "$(git rev-list "${{ github.event.release.tag_name }}"..master | tail -1)"
- name: Setup node
uses: actions/setup-node@v1
with:
node-version: '14.x'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm run test:unit
- name: Publish desktop app
run: npm run electron:build -- -p always # https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/recipes.html#upload-release-to-github
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

117
.github/workflows/deploy-site.yaml vendored Normal file
View File

@@ -0,0 +1,117 @@
name: Deploy site
on:
release:
types: [created] # will be triggered when a NON-draft release is created and published.
jobs:
aws-deploy: # see: https://github.com/undergroundwires/aws-static-site-with-cd
runs-on: ubuntu-latest
steps:
-
name: "Infrastructure: Checkout"
uses: actions/checkout@v2
with:
path: aws
repository: undergroundwires/aws-static-site-with-cd
-
name: "Infrastructure: Create AWS user profile & session name"
run: >-
bash "scripts/configure/create-user-profile.sh" \
--profile user \
--access-key-id ${{secrets.AWS_DEPLOYMENT_USER_ACCESS_KEY_ID}} \
--secret-access-key ${{secrets.AWS_DEPLOYMENT_USER_SECRET_ACCESS_KEY}} \
--region us-east-1 \
&& \
echo "::set-env name=SESSION_NAME::${{github.actor}}-${{github.event_name}}-$(echo ${{github.sha}} | cut -c1-8)"
working-directory: aws
-
name: "Infrastructure: Deploy IAM stack"
run: >-
bash "scripts/deploy/deploy-stack.sh" \
--template-file stacks/iam-stack.yaml \
--stack-name privacysexy-iam-stack \
--capabilities CAPABILITY_IAM \
--parameter-overrides "WebStackName=privacysexy-web-stack DnsStackName=privacysexy-dns-stack \
CertificateStackName=privacysexy-cert-stack RootDomainName=privacy.sexy" \
--region us-east-1 --role-arn ${{secrets.AWS_IAM_STACK_DEPLOYMENT_ROLE_ARN}} \
--profile user --session ${{ env.SESSION_NAME }}
working-directory: aws
-
name: "Infrastructure: Deploy DNS stack"
run: >-
bash "scripts/deploy/deploy-stack.sh" \
--template-file stacks/dns-stack.yaml \
--stack-name privacysexy-dns-stack \
--parameter-overrides "RootDomainName=privacy.sexy" \
--region us-east-1 \
--role-arn ${{secrets.AWS_DNS_STACK_DEPLOYMENT_ROLE_ARN}} \
--profile user --session ${{ env.SESSION_NAME }}
working-directory: aws
-
name: "Infrastructure: Deploy certificate stack"
run: >-
bash "scripts/deploy/deploy-stack.sh" \
--template-file stacks/certificate-stack.yaml \
--stack-name privacysexy-cert-stack \
--capabilities CAPABILITY_IAM \
--parameter-overrides "IamStackName=privacysexy-iam-stack RootDomainName=privacy.sexy DnsStackName=privacysexy-dns-stack" \
--region us-east-1 \
--role-arn ${{secrets.AWS_CERTIFICATE_STACK_DEPLOYMENT_ROLE_ARN}} \
--profile user --session ${{ env.SESSION_NAME }}
working-directory: aws
-
name: "Infrastructure: Deploy web stack"
run: >-
bash "scripts/deploy/deploy-stack.sh" \
--template-file stacks/web-stack.yaml \
--stack-name privacysexy-web-stack \
--parameter-overrides "CertificateStackName=privacysexy-cert-stack DnsStackName=privacysexy-dns-stack \
RootDomainName=privacy.sexy UseDeepLinks=true" \
--capabilities CAPABILITY_IAM \
--region us-east-1 \
--role-arn ${{secrets.AWS_WEB_STACK_DEPLOYMENT_ROLE_ARN}} \
--profile user --session ${{ env.SESSION_NAME }}
working-directory: aws
-
name: "App: Checkout"
uses: actions/checkout@v2
with:
path: site
ref: master # otherwise we don't get version bump commit
-
name: "App: Setup node"
uses: actions/setup-node@v1
with:
node-version: '14.x'
-
name: "App: Install dependencies"
run: npm ci
working-directory: site
-
name: "App: Run tests"
run: npm run test:unit
working-directory: site
-
name: "App: Build"
run: npm run build
working-directory: site
-
name: "App: Deploy to S3"
run: >-
bash "aws/scripts/deploy/deploy-to-s3.sh" \
--folder site/dist \
--web-stack-name privacysexy-web-stack --web-stack-s3-name-output-name S3BucketName \
--storage-class ONEZONE_IA \
--role-arn ${{secrets.AWS_S3_SITE_DEPLOYMENT_ROLE_ARN}} \
--region us-east-1 \
--profile user --session ${{ env.SESSION_NAME }}
-
name: "App: Invalidate CloudFront cache"
run: >-
bash "aws/scripts/deploy/invalidate-cloudfront-cache.sh" \
--paths "/*" \
--web-stack-name privacysexy-web-stack --web-stack-cloudfront-arn-output-name CloudFrontDistributionArn \
--role-arn ${{secrets.AWS_CLOUDFRONT_SITE_DEPLOYMENT_ROLE_ARN}} \
--region us-east-1 \
--profile user --session ${{ env.SESSION_NAME }}

26
.github/workflows/quality-checks.yaml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Quality checks
on: [ push, pull_request ]
jobs:
lint:
runs-on: ubuntu-latest
strategy:
matrix:
lint-command:
- npm run lint:vue
- npm run lint:yaml
- npm run lint:md
- npm run lint:md:relative-urls
- npm run lint:md:consistency
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v1
with:
node-version: 14.x
- name: Install dependencies
run: npm ci
- name: Lint
run: ${{ matrix.lint-command }}

23
.github/workflows/security-checks.yaml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: Security checks
on:
push:
pull_request:
schedule:
- cron: '0 0 * * 0'
jobs:
npm-audit:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Setup node
uses: actions/setup-node@v1
with:
node-version: 14.x
-
name: NPM audit
run: npm audit

View File

@@ -1,26 +1,25 @@
name: Run tests
name: Test
on:
push:
branches:
- '*'
- '!master'
on: [ push, pull_request ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
run-tests:
strategy:
matrix:
os: [macos, ubuntu, windows]
runs-on: ${{ matrix.os }}-latest
steps:
-
name: Checkout
uses: actions/checkout@v1
uses: actions/checkout@v2
-
name: Setup node
uses: actions/setup-node@v1
with:
node-version: '11.x'
node-version: '14.x'
-
name: Install dependencies
run: npm install
run: npm ci
-
name: Run tests
run: npm run test:unit

4
.gitignore vendored
View File

@@ -1,4 +1,6 @@
node_modules
/dist
.vs
.vscode
.vscode
#Electron-builder output
/dist_electron

4
.markdownlint.json Normal file
View File

@@ -0,0 +1,4 @@
{
"default": true,
"MD013": false
}

View File

@@ -1,40 +1,268 @@
# Changelog
- All notable changes to this project will be documented in this file.
## 0.7.5 (2020-09-14)
## [Unreleased]
* fix reverting (reinstalling) capabilities not working | [commit](https://github.com/undergroundwires/privacy.sexy/commit/939d838e3535bb1c9b00c8ea9dacb735ae41d700)
* fix tests and checks are not running on PRs | [commit](https://github.com/undergroundwires/privacy.sexy/commit/82d509129b4e4a5df4b84786a0d6842a7d26e888)
* fix the recycling bin option (#32) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/15db3118012a172a2191a2afad57084a65b34642)
* fix rendering issue in older edge/IE | [commit](https://github.com/undergroundwires/privacy.sexy/commit/6efed72bf25c2ddf0901caab7f22966ca13cd47a)
* fix pasting in search bar after page load showing no results | [commit](https://github.com/undergroundwires/privacy.sexy/commit/d1694341578288eeaf8b80caf9296a38d76789f0)
* fix typo | [commit](https://github.com/undergroundwires/privacy.sexy/commit/7dd15ed06433e0e6583ab0fa46a683ce6554bbea)
## [0.4.0] - 2020-01-11
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.4...0.7.5)
- Added search
- Some styling improvements
- Better organization of scripts + more scripts
## 0.7.4 (2020-09-12)
## [0.3.0] - 2020-01-09
* fix checked checkbox has blue border | [commit](https://github.com/undergroundwires/privacy.sexy/commit/4ae385b7fcea9014a68442714b7d99e2ee7df7d0)
* fix spectre protection getting single lined #31 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/22b23a9ece446c7f9abd4ede293051eb616ad50a)
* fix missing reg value in denying app access to account | [commit](https://github.com/undergroundwires/privacy.sexy/commit/3c13a9e837e06e097450b31d7eb0c0e6bf20cefb)
* fix wrong path in clear all firefox user profile settings | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ee66196d9a60f27d17ae7f62d02b4f119a47e6e0)
- Added support for grouping
- Added some meta tags
- Added "Disable NVIDIA telemetry" script
- Added legacy browser compatibility for fonts & changed main font
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.3...0.7.4)
## [0.2.0] - 2020-01-06
## 0.7.3 (2020-09-12)
- Fixed typo in generated code.
- Better URL validation for documentation links in `application.yaml`.
- Slightly faster parsing of `application.yaml`
- Styled no JS error that's shown when JavaScript is disabled.
- The default selection is now *None* & instruction text is shown in code box when nothing is selected.
- Added hyphen lines when rendering of long function names
- Changed subtitle: added version as footer instead.
* fix vscode settings file override and add more configs | [commit](https://github.com/undergroundwires/privacy.sexy/commit/a0d61728ead04b4455437f85820121a848db9e00)
* fix nvidia tweak error message, categorize and add reversibility | [commit](https://github.com/undergroundwires/privacy.sexy/commit/99a2035fdb0766a4dfc2753133eab0d7666516cd)
* improve CPU specific tweaks by conditional platform checks and reversibility | [commit](https://github.com/undergroundwires/privacy.sexy/commit/8df5faf4ef05a49da63973bd0fbb5c5d07d5bd93)
* fix wrong path to the main telemetry file | [commit](https://github.com/undergroundwires/privacy.sexy/commit/de4ac978bdda79573b36d355697b8a028d2c0beb)
* fix naming of firefox cleanup to mention profiles | [commit](https://github.com/undergroundwires/privacy.sexy/commit/3ab48b1cf5f7f934f07e468ef2318ccee07f530c)
* add reversibility and more scripts to denying app access with better structure | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1d465ee3189d0e5a827453b3f0eb4361efe23770)
* fix comment lines are being detected as duplicate in validation | [commit](https://github.com/undergroundwires/privacy.sexy/commit/b6ccb5927a20412976a54fd2215eb645092f98a8)
* add more detailed error message | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1f11c39773c12eccfb3efb898b58c2f6f37ab9ca)
* fix typo in a test | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1f19b2528a69383e63e579d2885f01cd804abf6c)
## [0.1.0] - 2019-12-31
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.2...0.7.3)
- Initial release
## 0.7.2 (2020-09-06)
## All releases
* update onesync documentation and do not recommend it as it breaks other apps | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f36d8bfc7848bb65ac0c641e318a689bf3816ccf)
* add reversibility for biometric disabling and do not recommend it | [commit](https://github.com/undergroundwires/privacy.sexy/commit/db74531cd4139615c6d595959217d3651f099019)
* fix bad highlighting of selected nodes when using keyboard navigation | [commit](https://github.com/undergroundwires/privacy.sexy/commit/255133af4dfae40171406648a3e2920f16d71cb3)
* add reversibility to removing bloatware | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c7b2a703128470a05f12c9c6e8002444def37ef8)
* fix indeterminate state being lost | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1f266c33535f72b69c65985bf2eff27cd2c5a104)
* fix wording in default text in text area | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ca63a0979ef55d07d09d9443e5cea9aa888870a5)
* add best practice suggestion to come back | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f4885b6f1c82752f2143934e336d6d1b1af03015)
- [Unreleased] : https://github.com/undergroundwires/privacy.sexy/compare/v0.4.0...HEAD
- [v0.4.0] : https://github.com/undergroundwires/privacy.sexy/compare/v0.3.0...v0.4.0
- [v0.3.0] : https://github.com/undergroundwires/privacy.sexy/compare/v0.2.0...v0.3.0
- [v0.2.0] : https://github.com/undergroundwires/privacy.sexy/compare/v0.1.0...v0.2.0
- [v0.1.0] : https://github.com/undergroundwires/privacy.sexy/releases/tag/v0.1.0
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.1...0.7.2)
## 0.7.1 (2020-09-04)
* fix some browsers (including firefox) downloading the script as a text file | [commit](https://github.com/undergroundwires/privacy.sexy/commit/8c17929151f9c4fa5f48564492bbf400ced95eea)
* rename screenshot image file | [commit](https://github.com/undergroundwires/privacy.sexy/commit/b8682a852a14ed6cf49986695d9510b840ac9d3d)
* fix new/changed script higlighting not working on production builds | [commit](https://github.com/undergroundwires/privacy.sexy/commit/8c38dd73d8c7b77d8d341c0389f4d7229f9b97fd)
* refactor unused imports | [commit](https://github.com/undergroundwires/privacy.sexy/commit/6badfef9daace0c5de3fd33652a82bfe22261b11)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.0...0.7.1)
## 0.7.0 (2020-09-02)
* [search] better (multilined) message when there are no results | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ec15af01dd020b364c2174fe562fd66227c2320c)
* [search] added clear/close button | [commit](https://github.com/undergroundwires/privacy.sexy/commit/d6fa9a2a03c0ebe68b94f0b80cc52b4e200c9213)
* move script generation to /generation | [commit](https://github.com/undergroundwires/privacy.sexy/commit/5df458739d076719e350ba194c4f3f772884fcdb)
* add auto-highlighting of selected/updated code | [commit](https://github.com/undergroundwires/privacy.sexy/commit/b789250cb89e2130b08e1a927df8181cf945dfeb)
* prompt admin priviliges automatically | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f8ba5c46e4923d9c35f200f8a08aa6437f7c0ecc)
* add removal of ghost (default0) telemetry user | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c262681011f39b4412669b6cf233476f676ca550)
* add more windows defender tweaks, categorization and reversibility | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1a34c7374ba56bafa0209bbb55c81b233bb419ed)
* fix NTP script documentation is on wrong place | [commit](https://github.com/undergroundwires/privacy.sexy/commit/3060ebf79cf242370433495cc3e1878b7581b202)
* updated dependencies to latest and audit fixes (#25) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c628aa9aef8ab7c815661d3c1711e7fbc65c69a2)
* categorize, fix and extend windows log files cleanup | [commit](https://github.com/undergroundwires/privacy.sexy/commit/594a14d6ca76cbd27a21877b8c373c1930589ca6)
* add more OneDrive cleanup scripts and categorize them | [commit](https://github.com/undergroundwires/privacy.sexy/commit/978d7d08638dd161082f239ed088b12302f29458)
* add disabling firefox telemetry | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f8b8b4c97ab734d5ba7370894b694993924388da)
* add disabling ccleaner telemetry | [commit](https://github.com/undergroundwires/privacy.sexy/commit/018b7e270f207aac926cb12f8069ebfcdce193ce)
* Add disabling of PowerShell 7+ telemetry (#29) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/456e40bedf9afcc846f9b13f1ea144cef6115cf6)
* categorize, fix, make scripts reversible in "UI for privacy", "security improvements" and "configure browsers" | [commit](https://github.com/undergroundwires/privacy.sexy/commit/532915b95da9fecd6b981d91bf489359e4e53caa)
* fix "Configure Defender" being in wrong category #28 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f709d6a566ed7846b677b383863deda9680a2a9c)
* do not hardcode capability versions and make them reversible | [commit](https://github.com/undergroundwires/privacy.sexy/commit/2afef4ea3d0d3d09aa1fa1eedba8493680bd8f10)
* exclude paint, wordpad and notepad from bloatware removal | [commit](https://github.com/undergroundwires/privacy.sexy/commit/d235dee95514a01745aef9479d07f88ffb4b40b8)
* add reversibility on category level | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f51e8859eeb32c944126d692cfe03a0320c8b568)
* refactor unused imports & variables | [commit](https://github.com/undergroundwires/privacy.sexy/commit/a23d28f2cfa2d64d45460697cf5ee9d6b5920752)
* fix search (got broken in b789250) with tests and refactorings | [commit](https://github.com/undergroundwires/privacy.sexy/commit/8bbe6ebf750f1a1cbab493fb99b5ea91f4e21609)
* update the screenshot to show off highlighting | [commit](https://github.com/undergroundwires/privacy.sexy/commit/b4aacea2a3e0bbcf2d8a79ff67f51c0f19e888a6)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.6.2...0.7.0)
## 0.6.2 (2020-08-16)
* 🐛 fixed disabling error reporting for november 2019 update | [commit](https://github.com/undergroundwires/privacy.sexy/commit/5967347b80976a519f6f4eb1972a62f3e600df2b)
* 🐛 fixed blank screen and icons on mac | [commit](https://github.com/undergroundwires/privacy.sexy/commit/7fac0fe79f252e8f9dda4f6f83cd6fa4ba2b539f)
* 🐛 fixed removing onedrive does not delete scheduled tasks | [commit](https://github.com/undergroundwires/privacy.sexy/commit/b6bfc2572740c0cd46d3bc0058fa767dd5fa862e)
* ⚙️ enhanced tweak to disable for office telemetry | [commit](https://github.com/undergroundwires/privacy.sexy/commit/afc3bfb3b8896f332c9a196973ded3dce8fd21e4)
* ✨ added script to clear dotnet telemery | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1663bfeac7b6580b1335ca5fcf3587b69c080c72)
* 🐛 fixed changing time server not working | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c69998c7cb29ffcf40f0af03b73150736581da69)
* 🔥 removed disabling ClickToRun as it breaks office | [commit](https://github.com/undergroundwires/privacy.sexy/commit/3d3380f27ebeea53f17f49974aaa89300ffaf2dd)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.6.1...0.6.2)
## 0.6.1 (2020-08-09)
* updated documentation | [commit](https://github.com/undergroundwires/privacy.sexy/commit/5963d2bac551083f9d16cce6b851abf0e8b88ce7)
* fixed typo in footer | [commit](https://github.com/undergroundwires/privacy.sexy/commit/5c15a7a64aaf24578a32713dec491bf494216303)
* more scripts can be reverted | [commit](https://github.com/undergroundwires/privacy.sexy/commit/831c014f977515454ee6dc664d77a8c434495501)
* moved windows connect now to security & recommended | [commit](https://github.com/undergroundwires/privacy.sexy/commit/6049a2b834d8d17af741f8d8f8b07cd15153b001)
* fixed mac / linux download links | [commit](https://github.com/undergroundwires/privacy.sexy/commit/4c8be45e287b5ea009d6f828f7f327f37850569e)
* tweaks to disable webcam, speech and compatibility telemetry | [commit](https://github.com/undergroundwires/privacy.sexy/commit/a5dbe66fc175e39397f296ab2ff703e9b0ab4d7c)
* refactorings | [commit](https://github.com/undergroundwires/privacy.sexy/commit/66d4d39d5bf3db305450514c6b6224654dafbfb2)
* fixed removing onedrive does not clean start menu / quick access | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1cc12195a3e9a11c590d3ed64d80299b50f74838)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.6.0...0.6.1)
## 0.6.0 (2020-07-26)
* fixed dead links in documentation | [commit](https://github.com/undergroundwires/privacy.sexy/commit/25ce236a7737decaf2eb9b8c29a4c4f34d43f770)
* runs tests on each push on the repository | [commit](https://github.com/undergroundwires/privacy.sexy/commit/73c426844a0330718a9ab7de12b61ca05e853323)
* code area now shows "how" before "why" | [commit](https://github.com/undergroundwires/privacy.sexy/commit/4ff4b52202b1c5dbfe2b80580bbe7d93132ab05c)
* support for desktop versions #20 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/04b9b59e14766ccd251474ad3710baf1f682fd49)
* reworked on footer & removed github icon | [commit](https://github.com/undergroundwires/privacy.sexy/commit/60a5a2aa4026d384bef9e6a203f1b7514a269c33)
* updated dependencies to latest | [commit](https://github.com/undergroundwires/privacy.sexy/commit/45816a2bccb3d11a50e3f2bc19c0a6cc2587deaa)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.5.0...0.6.0)
## 0.5.0 (2020-07-19)
* added ability to revert (#21) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/9c063d59defa6297c64f50b49403e8bd10620de9)
* search placeholder shows total scripts | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1d5225de07186f853f4cf7aedd4998f5d00c107a)
* do not collapse card when on "Search" and "Select" | [commit](https://github.com/undergroundwires/privacy.sexy/commit/dd7e1416b4df54bf71b719d4654db88769dc0994)
* opening a card scrolls to its content div | [commit](https://github.com/undergroundwires/privacy.sexy/commit/31d2067f076c3159483baec49975617dddbd158d)
* all cards in same line now have same height | [commit](https://github.com/undergroundwires/privacy.sexy/commit/a9f9e9044385d9aed3b5551fc6c6823e813fd1e5)
* patched loadash vulnerability (#18) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/92a7118d1c5013312772e075b9ee5a79c93710b8)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.10...0.5.0)
## 0.4.10 (2020-07-15)
* fixed script errors & added tests | [commit](https://github.com/undergroundwires/privacy.sexy/commit/9e722ddfb3825fb29d6298025baaaa033120d017)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.9...0.4.10)
## 0.4.9 (2020-07-14)
* disable office telemetry Disassembler0/Win10-Initial-Setup-Script#288 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/53cf595e1726ee3de79137fd566978fd512d218f)
* updated to may 2020 update | [commit](https://github.com/undergroundwires/privacy.sexy/commit/909c44d72a4a602ee8f27d06b6ec706c1e432ce1)
* simplified docker builds | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f27a2871d74e5117fc029be82caef12246e10879)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.8...0.4.9)
## 0.4.8 (2020-07-11)
* added more scripts #16 (#17) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/d8552c62ffea13ce62abce836c7dd4980eef6bb9)
* stopping services before disabling #16 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/628c16eb952495f5b3f6d794161b355f4b08b819)
* can disable features, capabilities & remove onedrive #16 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/30efbcc621eb83dd5a9c1e66b8f1f5350eb95006)
* updated one more typo (#19) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/d7a1325c0b7665ce712dc411965d00fc1d6fa384)
* more tweaks #16 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/2c4eb78c3f156cb0d033977cffbe7464697680f5)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.7...0.4.8)
## 0.4.7 (2020-06-30)
* removed HKU tweak as all HKU's are changed #10 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c937af8ee7da9aa95131e56abf7bf24800390fe6)
* Fixed types + script in "Clear Windows log files" (#15) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/461a4f122b342369db5cc08c5e30961c64e68cdd)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.6...0.4.7)
## 0.4.6 (2020-06-16)
* Fixed Some More Issues (#12) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/52d5713a99422cdf900aba819e49e998abac33cc)
* removed failing continuous deployment #14 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/583c5660d6ac934b845a044e013357aa91f61c15)
* Updated Some Tweaks (#11) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/0fc18459cde57684f00764815062f838f932aed5)
* Updated Some More Tweaks (#13) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/019b838925e963b7ec052ac76c6faf5650b9eb67)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.5...0.4.6)
## 0.4.5 (2020-06-13)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.4...0.4.5)
## 0.4.4 (2020-05-24)
* fixed close card button not being visible & cleanup | [commit](https://github.com/undergroundwires/privacy.sexy/commit/0d2efe5b05aa965458b78b8fa43754ce2f4fe11b)
* new footer with privacy policy | [commit](https://github.com/undergroundwires/privacy.sexy/commit/e2ab124fb799f56ada3570fdc911361cb803e889)
* one command to lint everything "npm run lint" | [commit](https://github.com/undergroundwires/privacy.sexy/commit/bb98d20637cbf1d524ebb2973e308773006e3153)
* fix "group by" overflows on smaller screens | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c668a97950a1cb7c8bf2a7fd8a72d1101e65e8ce)
* clicking outside of a card closes it | [commit](https://github.com/undergroundwires/privacy.sexy/commit/aab8f21a8d8dbed54798af581e6e1ad9e86a4be1)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.3...0.4.4)
## 0.4.3 (2020-05-23)
* removed redundant documentation | [commit](https://github.com/undergroundwires/privacy.sexy/commit/749a140eb8dba09cb67fec2f8dec937e66e3cff5)
* fixed broke link | [commit](https://github.com/undergroundwires/privacy.sexy/commit/97b7e03233d9718a8df30cb01ce06ca9489a0295)
* simplified heading | [commit](https://github.com/undergroundwires/privacy.sexy/commit/226074c5342f7463c06fcff1457d352ca30295a3)
* reading version from package.json instead of version file #5 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/691f989682179016ddcbf55a05cded29155288c9)
* automatically increases patch number #5 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/3e3bc07576f7c7e74e3e11fc7d197cbb9a9fb8c0)
* using deployment operations from aws-static-site-with-cd | [commit](https://github.com/undergroundwires/privacy.sexy/commit/997be7113f676888892ffa35566d9ebb58a3e9ea)
* automated using bump-everywhere + more quality checks (#8) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/4a91e8ccd8a707bc6bea34ee28cff7fa4f66ee2f)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.2...0.4.3)
## 0.4.2 (2020-02-29)
* added missing semicolon for masking | [commit](https://github.com/undergroundwires/privacy.sexy/commit/e63ac4ae67da68243a525af149ff30e5d485b641)
* set font on input | [commit](https://github.com/undergroundwires/privacy.sexy/commit/0c39a06be5e4b0a2031ad5e9f5220dd669afee53)
* shortened all HKEY paths | [commit](https://github.com/undergroundwires/privacy.sexy/commit/802b36bdd8dcc1f0a2853fe7da2ea2fccd69a88c)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.1...0.4.2)
## 0.4.1 (2020-01-11)
* fixed search bug | [commit](https://github.com/undergroundwires/privacy.sexy/commit/31364bdfec503af09ffbb58044a17dfb833fc8d9)
* hide grouping while searching | [commit](https://github.com/undergroundwires/privacy.sexy/commit/92f1a36bcb1e1fe7c90efe8ccd3ede55991e9d9c)
* 👀🔍 showing search queries | [commit](https://github.com/undergroundwires/privacy.sexy/commit/97a7747933d2b515cc03ab8243e6a8ae702ef16a)
* more efficient queries with single lowercase | [commit](https://github.com/undergroundwires/privacy.sexy/commit/19813b691746d98670823025c460480400e34b6e)
* using right 🔍 input type | [commit](https://github.com/undergroundwires/privacy.sexy/commit/0ce354ea0956391ad3f37b252daac1127bfc601a)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.0...0.4.1)
## 0.4.0 (2020-01-11)
* 🔍 support for search | [commit](https://github.com/undergroundwires/privacy.sexy/commit/89862b2775703257b9dc2e19fbebde2c0d0fbda0)
* more scripts & better organized | [commit](https://github.com/undergroundwires/privacy.sexy/commit/95baf3175b0d2c7df516f7893a96346b94ac8eca)
* refactorings | [commit](https://github.com/undergroundwires/privacy.sexy/commit/e3f82e069e305f6d94eab335470c8e7b44295dd6)
* more margin for the scripts | [commit](https://github.com/undergroundwires/privacy.sexy/commit/5ea46ecbf52236953d19f09a8eade08b83e6cd34)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.3.0...0.4.0)
## 0.3.0 (2020-01-09)
* added description & more descriptive title | [commit](https://github.com/undergroundwires/privacy.sexy/commit/99576340b648550149871e2c0fe0b0d8c2dd0d7c)
* allow robots | [commit](https://github.com/undergroundwires/privacy.sexy/commit/eee0e785ec2c5e6bed53d21b4126a57773e35dba)
* removed unused references | [commit](https://github.com/undergroundwires/privacy.sexy/commit/cfd888f3afc5c260a0a4a73f2843b86b9f1df2cd)
* 🚫 disable NVIDIA telemetry | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ab28f4ed8538d51e1777c86302a63a0cd9c3cb2a)
* backwards compatibility for fonts | [commit](https://github.com/undergroundwires/privacy.sexy/commit/4bc13e11926a6df77079646499e799742153b4ab)
* added back meta needed for responsiveness | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ed872ef3d9f6c92afc0ce0d06998c60463a8b4e8)
* fancy-font is renamed to main and now used | [commit](https://github.com/undergroundwires/privacy.sexy/commit/6825001c61426194dc363b96b57a321241f3ba57)
* added support for grouping | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ec6b3c54072a77bb4305da1c234db6c649218b88)
* less hyphens as it looks better on mobile | [commit](https://github.com/undergroundwires/privacy.sexy/commit/e0b080af69157f46ba12e2c25e794f5384671b51)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.2.0...0.3.0)
## 0.2.0 (2020-01-06)
* added GitHub Actions badge for build & deploy | [commit](https://github.com/undergroundwires/privacy.sexy/commit/a229aca68a92bbcd8e8176ac1dd25ce03509e074)
* more badges 📛🏆📜 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/090e8319091044e53484ba8338510f6fb7c3cb80)
* typo fixes + whitespace refactorings | [commit](https://github.com/undergroundwires/privacy.sexy/commit/e99f210c9dcf61a21e445e2a331384b6066f2c98)
* switched content information to "why" section | [commit](https://github.com/undergroundwires/privacy.sexy/commit/beb3c8339f83a224ca66ad8a911a9265ffe7c9c0)
* fixed contribution URL | [commit](https://github.com/undergroundwires/privacy.sexy/commit/7b4277d7706ccf6ba7e4b7b01aa46f8e3852cfc6)
* fixed wrong relation + lighter style | [commit](https://github.com/undergroundwires/privacy.sexy/commit/8d05b03c9f3c9fc015be6615da8c283809712065)
* better URL validation | [commit](https://github.com/undergroundwires/privacy.sexy/commit/aff463dd64fecff92a786fcba88621dff6b1cf73)
* refactoring to new function | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c646c102730481c3f4648eb714dc0a84ce35b13c)
* optimized find queries & refactorings | [commit](https://github.com/undergroundwires/privacy.sexy/commit/d38f6cd6a8b33e11df854c7abea05974dc04d4ce)
* 🎨 styled no JS error | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c359f1d89c6874b3cc94154b993e33f58bd32268)
* simplified finding duplicates | [commit](https://github.com/undergroundwires/privacy.sexy/commit/57037aaefcc0e80f0f4719cea89568490a731028)
* fixed maintainability badge URL | [commit](https://github.com/undergroundwires/privacy.sexy/commit/aaea47e7d15fe41dea26968db0107a0c53d108f3)
* fixed wrong line dumps | [commit](https://github.com/undergroundwires/privacy.sexy/commit/5ccc7c59528885ae7729197df3dfa00f924a2b3f)
* refactorings in parsing | [commit](https://github.com/undergroundwires/privacy.sexy/commit/2aa3742e30646bf1d1f3779419d161c3fb6c4808)
* using free function | [commit](https://github.com/undergroundwires/privacy.sexy/commit/20020af7c1d8de13948d8761fd4e7f0affb2badb)
* default selection is now none | [commit](https://github.com/undergroundwires/privacy.sexy/commit/3140cc663b86394d543de90228aa53e6a304d8d9)
* added hyphen lines for longer names | [commit](https://github.com/undergroundwires/privacy.sexy/commit/cced601d686d550f4225018e5311b7433efbb5ae)
* more descriptive subtitle | [commit](https://github.com/undergroundwires/privacy.sexy/commit/2cf9214b14d9720f747a71b3864ba7a28acf0ff4)
* added footer with version | [commit](https://github.com/undergroundwires/privacy.sexy/commit/10a34fae2f1a219ec52db0c74edb39b46ebd8abc)
* using font variables | [commit](https://github.com/undergroundwires/privacy.sexy/commit/60e6348dc8d53f1e81ebdb2ec0e1962aac1e9842)
* code-gen refactorings | [commit](https://github.com/undergroundwires/privacy.sexy/commit/246e753ddc9dc8bf630e538663584bf3423cc749)
* added text when nothing is chosen | [commit](https://github.com/undergroundwires/privacy.sexy/commit/a7da75d4428090423b692ce45423f5bd300d8442)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.1.0...0.2.0)
## 0.1.0 (2019-12-31)
Initial release | [commits](https://github.com/undergroundwires/privacy.sexy/commit/4e7f244190c6ffbf7b20443e3e69cf2402c4268a)

45
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,45 @@
# Contributing
- Love your input! Contributing to this project should be as easy and transparent as possible, whether it's:
- Reporting a bug
- Discussing the current state of the code
- Submitting a fix
- Proposing new features
- Becoming a maintainer
## Pull Request Process
- [GitHub flow](https://guides.github.com/introduction/flow/index.html) is used
- Your pull requests are actively welcomed.
- The steps:
1. Fork the repo and create your branch from master.
2. If you've added code that should be tested, add tests.
3. If you've changed APIs, update the documentation.
4. Ensure the test suite passes.
5. Make sure your code lints.
6. Issue that pull request!
- 🙏 DO
- Document your changes in the pull request
- ❗ DON'T
- Do not update the versions, current version is only [set by the maintainer](./img/architecture/gitops.png) and updated automatically by [bump-everywhere](https://github.com/undergroundwires/bump-everywhere)
## Guidelines
### Extend scripts
- Create a [pull request](#Pull-Request-Process) for [application.yaml](./src/application/application.yaml)
- 🙏 For any new script, try to add `revertCode` that'll revert the changes caused by the script.
- See [typings](./src/application/application.yaml.d.ts) for documentation as code.
### Handle the state in presentation layer
- There are two types of components:
- **Stateless**, extends `Vue`
- **Stateful**, extends [`StatefulVue`](./src/presentation/StatefulVue.ts)
- The source of truth for the state lies in application layer (`./src/application/`) and must be updated from the views if they're mutating the state
- They mutate or/and reacts to changes in [application state](src/application/State/ApplicationState.ts).
- You can react by getting the state and listening to it and update the view accordingly in [`mounted()`](https://vuejs.org/v2/api/#mounted) method.
## License
By contributing, you agree that your contributions will be licensed under its GNU General Public License v3.0.

View File

@@ -1,20 +1,12 @@
# +-+-+-+-+-+ +-+-+-+-+-+
# |B|u|i|l|d| |S|t|a|g|e|
# +-+-+-+-+-+ +-+-+-+-+-+
# Build
FROM node:lts-alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# For testing purposes, it's easy to run http-server on lts-alpine such as continuing from here:
# RUN npm install -g http-server
# EXPOSE 8080
# CMD [ "http-server", "dist" ]
# +-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+
# |P|r|o|d|u|c|t|i|o|n| |S|t|a|g|e|
# +-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+
# Production stage
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
EXPOSE 80

137
README.md
View File

@@ -1,126 +1,87 @@
# privacy.sexy
![Build & deploy status](https://github.com/undergroundwires/privacy.sexy/workflows/Build%20&%20deploy/badge.svg)
![Vulnerabilities](https://snyk.io/test/github/undergroundwires/privacy.sexy/badge.svg)
[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/undergroundwires/privacy.sexy/issues)
> Enforce privacy & security best-practices on Windows, because privacy is sexy 🍑🍆
[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](./CONTRIBUTING.md)
[![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/undergroundwires/privacy.sexy.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/undergroundwires/privacy.sexy/context:javascript)
[![Maintainability](https://api.codeclimate.com/v1/badges/3a70b7ef602e2264342c/maintainability)](https://codeclimate.com/github/undergroundwires/privacy.sexy/maintainability)
[![Tests status](https://github.com/undergroundwires/privacy.sexy/workflows/Test/badge.svg)](https://github.com/undergroundwires/privacy.sexy/actions)
[![Quality checks status](https://github.com/undergroundwires/privacy.sexy/workflows/Quality%20checks/badge.svg)](https://github.com/undergroundwires/privacy.sexy/actions)
[![Security checks status](https://github.com/undergroundwires/privacy.sexy/workflows/Security%20checks/badge.svg)](https://github.com/undergroundwires/privacy.sexy/actions)
[![Bump & release status](https://github.com/undergroundwires/privacy.sexy/workflows/Bump%20&%20release/badge.svg)](https://github.com/undergroundwires/privacy.sexy/actions)
[![Deploy status](https://github.com/undergroundwires/privacy.sexy/workflows/Build%20&%20deploy/badge.svg)](https://github.com/undergroundwires/privacy.sexy/actions)
[![Auto-versioned by bump-everywhere](https://github.com/undergroundwires/bump-everywhere/blob/master/badge.svg?raw=true)](https://github.com/undergroundwires/bump-everywhere)
Web tool to generate scripts for enforcing privacy & security best-practices such as stopping data collection of Windows and different softwares on it.
> because privacy is sexy 🍑🍆
## Get started
[https://privacy.sexy](https://privacy.sexy)
- Online version: [https://privacy.sexy](https://privacy.sexy)
- or download latest desktop version for [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.5/privacy.sexy-Setup-0.7.5.exe), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.5/privacy.sexy-0.7.5.AppImage), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.5/privacy.sexy-0.7.5.dmg)
- 💡 Come back regularly to apply latest version for stronger privacy and security.
## Why privacy.sexy
[![privacy.sexy application](img/screenshot.png)](https://privacy.sexy)
- You don't need to run any compiled software on your system, just run the generated scripts.
- It's open source, both application & infrastructure is 100% transparent
- Fully automated C/CD pipeline to AWS for provisioning serverless infrastructure using GitHub actions.
- Have full visibility into what the tweaks do as you enable them.
## Why
- Rich tweak pool to harden security & privacy of the OS and other software on it
- Free (both free as in beer and free as in speech)
- No need to run any compiled software that has access to your system, just run the generated scripts
- Have full visibility into what the tweaks do as you enable them
- Ability to revert (undo) applied scripts
- Easily extendable
- Everything is open-source and automated (both application and its infrastructure)
## Extend scripts
Fork it & add more scripts in `src/application/application.yml` and send a pull request 👌
- Fork it & add more scripts in [application.yaml](src/application/application.yaml) and send a pull request 👌
- 📖 More: [extend scripts | CONTRIBUTING.md](./CONTRIBUTING.md#extend-scripts)
## Commands
- Setup and run
- For development:
- `npm install` to project setup.
- `npm run serve` to compile & hot-reload for development.
- Production (using Docker):
- Build `docker build -t undergroundwires/privacy.sexy .`
- Run `docker run -it -p 8080:8080 --rm --name privacy.sexy-1 undergroundwires/privacy.sexy`
- Prepare for production: `npm run build`
- Run tests: `npm run test:unit`
- Lint and fix files: `npm run lint`
- Project setup: `npm install`
- Testing
- Run unit tests: `npm run test:unit`
- Lint: `npm run lint`
- **Desktop app**
- Development: `npm run electron:serve`
- Production: `npm run electron:build` to build an executable
- **Webpage**
- Development: `npm run serve` to compile & hot-reload for development.
- Production: `npm run build` to prepare files for distribution.
- Or run using Docker:
1. Build: `docker build -t undergroundwires/privacy.sexy:0.7.5 .`
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.7.5 undergroundwires/privacy.sexy:0.7.5`
## Architecture
### Application
- Powered by **TypeScript** + **Vue.js** 💪
- Powered by **TypeScript**, **Vue.js** and **Electron** 💪
- and driven by **Domain-driven design**, **Event-driven architecture**, **Data-driven programming** concepts.
- Application uses highly decoupled models & services in different DDD layers.
- **Domain layer** is where the application is modelled with validation logic.
- **Presentation Layer**
- Consists of Vue.js components & UI stuff.
- Consists of Vue.js components and other UI-related code.
- Desktop application is created using [Electron](https://www.electronjs.org/).
- Event driven as in components simply listens to events from the state and act accordingly.
- **Application Layer**
- Keeps the application state
- The [state](src/application/State/ApplicationState.ts) is a mutable singleton & event producer.
- The application is defined & controlled in a [single YAML file](`\application\application.yaml`) (see [Data-driven programming](https://en.wikipedia.org/wiki/Data-driven_programming))
- The application is defined & controlled in a [single YAML file](src/application/application.yaml) (see [Data-driven programming](https://en.wikipedia.org/wiki/Data-driven_programming))
![DDD + vue.js](docs/app-ddd.png)
![DDD + vue.js](img/architecture/app-ddd.png)
### AWS Infrastructure
- The application runs in AWS 100% serverless and automatically provisioned using [CloudFormation files](/aws) and GitHub Actions.
- Maximum security & automation and minimum AWS costs were the highest priorities of the design.
[![AWS solution](img/architecture/aws-solution.png)](https://github.com/undergroundwires/aws-static-site-with-cd)
![AWS solution](docs/aws-solution.png)
- It uses infrastructure from the following repository: [aws-static-site-with-cd](https://github.com/undergroundwires/aws-static-site-with-cd)
- Runs on AWS 100% serverless and automatically provisioned using [GitHub Actions](.github/workflows/).
- Maximum security & automation and minimum AWS costs are the highest priorities of the design.
#### GitOps: CI/CD to AWS
- CI/CD is fully automated for this repo using different GIT events & GitHub actions.
- Versioning, tagging, creation of `CHANGELOG.md` and releasing is automated using [bump-everywhere](https://github.com/undergroundwires/bump-everywhere) action
- Everything that's merged in the master goes directly to production.
- Deploy infrastructure ► Deploy web application ► Invalidate CloudFront Cache
- See more at [build-and-deploy.yaml](.GitHub/workflows/build-and-deploy.yaml)
![CI/CD to AWS with GitHub Actions](docs/ci-cd.png)
##### CloudFormation
![CloudFormation design](docs/aws-cloudformation.png)
- AWS infrastructure is defined as code with following files:
- `iam-stack`: Creates & updates the deployment user.
- Everything in IAM layer is fine-grained using least privileges principle.
- Each deployment step has its own temporary credentials with own permissions.
- `certificate-stack.yaml`
- It'll generate SSL certification for the root domain and www subdomain.
- ❗ It [must](https://aws.amazon.com/premiumsupport/knowledge-center/cloudfront-invalid-viewer-certificate/) be deployed in `us-east-1` to be able to be used by CloudFront by `web-stack`.
- It uses CustomResource and a lambda instead of native `AWS::CertificateManager::Certificate` because:
- Problem:
- AWS variant waits until a certificate is validated.
- There's no way to automate validation without workaround.
- Solution:
- Deploy a lambda that deploys the certificate (so we don't wait until certificate is validated)
- Get DNS records to be used in validation & export it to be used later.
- `web-stack.yaml`: It'll deploy S3 bucket and CloudFront in front of it.
- `dns-stack.yaml`: It'll deploy Route53 hosted zone
- Each time Route53 hosted zone is re-created it's required to update the DNS records in the domain registrar. See *Configure your domain registrar*.
- I use cross stacks instead of single stack or nested stacks because:
- Easier to test & maintain & smaller files and different lifecycles for different areas.
- It allows to deploy web bucket in different region than others as other stacks are global (`us-east-1`) resources.
##### Initial deployment
- ❗ Prerequisite: A registered domain name for website.
1. **Configure build agent (GitHub actions)**
- Deploy manually `iam-stack.yaml` with stack name `privacysexy-iam-stack` (to follow the convention)
- It'll give you deploy user. Go to console & generate secret id + key (Security credentials => Create access key) for the user [IAM users](https://console.aws.amazon.com/iam/home#/users).
- 🚶 Deploy secrets:
- Add secret id & key in GitHub Secrets.
- `AWS_DEPLOYMENT_USER_ACCESS_KEY_ID`, `AWS_DEPLOYMENT_USER_SECRET_ACCESS_KEY`
- Add more secrets given from Outputs section of the CloudFormation stack.
- Run GitHub actions to deploy rest of the application.
- It'll run `certificate-stack.yaml` and then `iam-stack.yaml`.
2. **Configure your domain registrar**
-**Web stack will fail** after DNS stack because you need to validate your domain.
- 🚶 Go to your domain registrar and change name servers to NS values
- `dns-stack.yaml` outputs those in CloudFormation stack.
- You can alternatively find those in [Route53](https://console.aws.amazon.com/route53/home#hosted-zones)
- When nameservers of your domain updated, the certification will get validated automatically, you can then delete the failed stack in CloudFormation & re-run the GitHub actions.
## Thank you for the awesome projects 🍺
- [Vue.js](https://vuejs.org/) the only big JavaScript framework that's not backed by companies that make money off your data.
- [liquor-tree](https://GitHub.com/amsik/liquor-tree) for the awesome & super extensible tree component.
- [Ace](https://ace.c9.io/) for code box.
- [FileSaver.js](https://GitHub.com/eligrey/FileSaver.js) for save file dialog.
- [chai](https://GitHub.com/chaijs/chai) & [mocha](https://GitHub.com/mochajs/mocha) for making testing fun.
- [js-yaml-loader](https://GitHub.com/wwilsman/js-yaml-loader) for ahead of time loading `application.yml`
- [v-tooltip](https://GitHub.com/Akryum/v-tooltip) takes seconds to have a tooltip, exactly what I needed.
[![CI/CD to AWS with GitHub Actions](img/architecture/gitops.png)](.github/workflows/)

View File

@@ -1 +0,0 @@
<mxfile host="www.draw.io" modified="2019-12-27T14:40:11.720Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36" etag="6t_Q0ZRAKXZ_lLm1WdcF" version="12.4.3" type="device" pages="1"><diagram id="pFg2tUHn5hOZkmQyf_J4" name="Page-1">7Vvtd6I4F/9r+lEOEMLLR23rzJzdme3qPLMvXzwUomaLxA2x1f3rnwSIEoIWFdqd3bXnKLmEJNz7u69Jb8DtavuBhuvlZxKj5MY24+0NuLux7SAA/FsQdgXBD8yCsKA4LkjWgTDFf6GSKLttcIwypSMjJGF4rRIjkqYoYgotpJS8qN3mJFFnXYcLpBGmUZjo1F9wzJblW0DzQP+I8GIpZ7bM8s4qlJ1LQrYMY/JSIYH7G3BLCWHF1Wp7ixLBO8mX4rnxkbv7hVGUsjYPfB19Hf0+Wf6+DcbR9Mtksvm2hANYjPIcJpvyhe/IKsQpp03QAmeMhrRcPttJnqwJThmi9898ZsFe6waM9m9n8kYcZksUl40lWyWyE6PkCd2ShFBOSUnKBxzNcZJI0o0NoCn+OD0JH1HyQDLMMEn5vQiJSfmNZ0QZ5hL6sdbhkTBGVpUOwwQvxA1G1pxKNizBKZ9dAkVMEpZd9oPz91iL11xtFwLPBpnPcYSMDNFn/psZVHJlVpL4M7okSuGIhaBthVRK5gMiK8Tojncp79qeXzxSqgmQ+H85gA66BWlZwRu0S6iXMF/sRz4ggV+UYDgDGJatIUPDASWbNN7L+WWJGZquQ8GSuxfOPFX2e0xYNZmXMFCxwYEQe8GjaXbDXslNyd1A564r9bTKXuCYffEXvM5fiUW8yo1UlZvNEH9VM3KdGoXR0yKXXYXb8/zDu+STDbN1RUdkY463QoKjcj13S8aEFR4KTtjjKE5tA3M7PMccFdSI+Iz2OA5ZyH8EPeO/4Sr8i6SD8CUbZAylEU4ENTea4y/8xrRQqtmUT4m55s0ist7NZsNfprPbhGxijkvfWKeLDjAxMA1gBpWP76kgcW0dJIGOEUm7BiJ/bp/idPL8FMApjNFk+8N6+/NA18CcBWNK+DvXsdJo3ea8Z0XG/G8s5h9x0xZjpNwLHHg3tiv37jDlAxX4SQkVHNBM9RCYI9iku3s0KZ7gQsOt2eiKHog3LIMGy5btkiuN2K3bd45Ehxv1jGxohD5FYj0j3iyu1F6R4P485343RslXrZJrAg1w0i1UASdpnQNON0lTTjJHm+gJdY841xwC4J2HONvzLMv91yAuA90gDToq0mDgGa7zvmDTI0+JM/OBJDjadQu45mDzCJ7Kzn87KB0JvHV4rQsOdgIexwlU8EDdTEHfkAMr0WkH0VMjeFwNPBOODsRJcnH/uca3NFRUcH8GOzJXAKjmyrGC97VVnh6JCXHw+DTMQfc5THlErCfKjcjrW+4NYgRGdFjvbFWsdmbrsPVgcHvKfHZhTmquCDiNrggYsCHUBj0J2NcEfJ/OCYc9z07cRMR7j1y67kJcffz69WGqiRrFCyS1S5gEsiBpmNwfqCM1Wz70+ZEIkeai+wMxtit1NtwwogqW85zufq02fhODGVA277bl4EVrV209IIo5rwRY7k4m1YW6n+BVmZawkC4Qe11pBGNO4oKiJGT4Wa23NUm4fPRBOMFKFO2qlRPoBgb0LMf2i29PHbB4vXKMarWsNqzt2IbFAed5rmfbZmB558xSMEebJcfm/tUvh6ulB08TFOdeKXsPZBZOTtZH7avg5baEl/028OKiNZQigQoDD14GL+Aq8PIDeHrcngEli0//smpUil4GOxTSgYhyko1YnyALfzSukAZm0GHdybUMT5U2MC3dATZUI72+wmlbz/xVbq4pfg6jnZGhrZ6XfWcGRkK7amFO7Y30bWGAr+ZXPN9SXIt7oQfbF7nLce2gBp++bYqjgSqf63vyUKdw8aqHclvip5SXaXCnUD5zLaQsqxdI2Y5v+JWBaoUB4LYC2JDScFfptt6j4piC1HZvHLuG12LE2tNyeDKfZ6gfhOtpA0/1GE43ZMPfx4zROiG6vbw+Edhi9qsM7vl1JQ3grUMWIBoyCejUgrbVAHCuBlhWYCuyHgTXKcQbgECPxT9gttw8fv8BlPlqAEUiJi8tUxj88SqkT4NFzoAuYya3VhFym6sGPQVNz2vPu/92v8tcb/fTty+TzAzmA0uT+08UL/KjE8MoQpmwAJ9i/s6YvUkt+9azgDX+59WyKUkOpScNMg3AOn7IwndUhwVB220Qx+kJRXri9TG8JEK6JCaqHtnh6o0jxVdUoGXmH9FrQ5/3hzgajXtnNaXjOnfCf0DLUVMrv5N4qn46B/r2ZQEUcLiP24dh/FsZ1gXtykjnBlB8veryPbdNACXPSfhNHO3UsTbKWk8fhkkizvMJcYbxu6QRHUK+IYa6APID0zDBlTGS3CSGgeF7PAfJt0UDUxW7C4Im5J6rAC6oQd6EhgUq9bVeFMDR9NdvowD9Y1zfx3wz69+XKQc6ro/j/xSubUv11t2kxrC2twi9GuTaIhlaavbrgnbllXOxq53daGe8Zf4kz3H2j2V9k3TyXmb6WLLbV6xyXBF62I/oagcCng5FeisWNns53RL+L0M6eN6ldtIleNq6/cLcdQ4fLZEOLoQPdKChhtqOC7jN5jmV5zqO67mB86YIatjDeAU95xxYF42HkPGENc0pttn2GPujz1l1QNFVCSw0a9Jr2DgKGmogltnBGeVmruvVr32U/kI5f/tU4b3a/la506UKnwTapQ7gEOFAt7YRuPfWV2q5452y7bB+jKa1zgNvf8xvd5jJdDzXCuR3K6V/yxjb1l3Lp+FnTuAOhmro/K8w17owF8Zxfiaw2TZ2Yu4CDW+O/p87jfW6vqq+tqdBphrQlkKtVtM6s3DmSQunVPOOV/C6Co1bm8YTHa+Ibq6SoYxtNYvwgfumtSZfDmDWlF80Ofma6s/9CEVRk4bWFXmF4zhPfpqiEtVltghM3jHWsM/XPHHaZv9vsIXZP/wvMbj/Pw==</diagram></mxfile>

View File

@@ -1,211 +0,0 @@
AWSTemplateFormatVersion: '2010-09-09'
Description: Creates certificate for the root + www subdomain. !! It must be deployed in us-east-1 to be able to be used by CloudFront.
Parameters:
RootDomainName:
Type: String
Default: privacy.sexy
Description: The root DNS name of the website e.g. privacy.sexy
AllowedPattern: (?!-)[a-zA-Z0-9-.]{1,63}(?<!-)
ConstraintDescription: Must be a valid root domain name
IamStackName:
Type: String
Default: privacysexy-iam-stack
Description: Name of the IAM stack.
Resources:
# The lambda workaround exists to be able to automate certificate deployment.
# Problem:
# Normally AWS AWS::CertificateManager::Certificate waits until a certificate is validated
# And there's no way to get validation DNS records from it to validate it.
# Solution:
# Deploy a lambda that deploys the certificate (so we don't wait until certificate is validated)
# Get DNS records to be used in validation & export it to be used later.
AcmCertificateForHostedZone:
Type: Custom::VerifiableCertificate #A Can use AWS::CloudFormation::CustomResource or Custom::String
Properties:
ServiceToken: !GetAtt ResolveCertificateLambda.Arn
# Lambda gets the following data:
RootDomainName: !Ref RootDomainName # Lambda will create both for root and www.root
Tags:
-
Key: Name
Value: !Ref RootDomainName
-
Key: Application
Value: privacy.sexy
ResolveCertificateLambda:
Type: AWS::Lambda::Function
Properties:
Description: Deploys certificate for root domain name + www and returns immediately arn + verification records.
Role:
Fn::ImportValue: !Join [':', [!Ref IamStackName, ResolveCertificateLambdaRoleArn]]
FunctionName: !Sub ${AWS::StackName}-cert-resolver-lambda # StackName- required for role to function
Handler: index.handler
Runtime: nodejs12.x
Timeout: 30
Tags:
-
Key: Application
Value: privacy.sexy
Code:
# Inline script is not the best way. Some variables are named shortly to not exceed the limit 4096 but it's the cheapest way (no s3 file)
ZipFile: >
'use strict';
const aws = require('aws-sdk');
const acm = new aws.ACM();
const log = (t) => console.log(t);
exports.handler = async (event, context) => {
log(`Request recieved:\n${JSON.stringify(event)}`);
const userData = event.ResourceProperties;
const rootDomain = userData.RootDomainName;
let data = null;
try {
switch(event.RequestType) {
case 'Create':
data = await handleCreateAsync(rootDomain, userData.Tags);
break;
case 'Update':
data = await handleUpdateAsync();
break;
case 'Delete':
data = await handleDeleteAsync(rootDomain);
break;
}
await sendResponseAsync(event, context, 'SUCCESS', data);
} catch(error) {
await sendResponseAsync(event, context, 'ERROR', {
title: `Failed to ${event.RequestType}, see error`,
error: error
});
}
}
async function handleCreateAsync(rootDomain, tags) {
const { CertificateArn } = await acm.requestCertificate({
DomainName: rootDomain,
SubjectAlternativeNames: [`www.${rootDomain}`],
Tags: tags,
ValidationMethod: 'DNS',
}).promise();
log(`Cert requested:${CertificateArn}`);
const waitAsync = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const maxAttempts = 10;
let options = undefined;
for (let attempt = 0; attempt < maxAttempts && !options; attempt++) {
await waitAsync(2000);
const { Certificate } = await acm.describeCertificate({ CertificateArn }).promise();
if(Certificate.DomainValidationOptions.filter((o) => o.ResourceRecord).length === 2) {
options = Certificate.DomainValidationOptions;
}
}
if(!options) {
throw new Error(`No records after ${maxAttempts} attempts.`);
}
return getResponseData(options, CertificateArn, rootDomain);
}
async function handleDeleteAsync(rootDomain) {
const certs = await acm.listCertificates({}).promise();
const cert = certs.CertificateSummaryList.find((cert) => cert.DomainName === rootDomain);
if (cert) {
await acm.deleteCertificate({ CertificateArn: cert.CertificateArn }).promise();
log(`Deleted ${cert.CertificateArn}`);
} else {
log('Cannot find'); // Do not fail, delete can be called when e.g. CF fails before creating cert
}
return null;
}
async function handleUpdateAsync() {
throw new Error(`Not yet implemented update`);
}
function getResponseData(options, arn, rootDomain) {
const findRecord = (url) => options.find(option => option.DomainName === url).ResourceRecord;
const root = findRecord(rootDomain);
const www = findRecord(`www.${rootDomain}`);
const data = {
CertificateArn: arn,
RootVerificationRecordName: root.Name,
RootVerificationRecordValue: root.Value,
WwwVerificationRecordName: www.Name,
WwwVerificationRecordValue: www.Value,
};
return data;
}
/* cfn-response can't async / await :( */
async function sendResponseAsync(event, context, responseStatus, responseData, physicalResourceId) {
return new Promise((s, f) => {
var b = JSON.stringify({
Status: responseStatus,
Reason: `See the details in CloudWatch Log Stream: ${context.logStreamName}`,
PhysicalResourceId: physicalResourceId || context.logStreamName,
StackId: event.StackId,
RequestId: event.RequestId,
LogicalResourceId: event.LogicalResourceId,
Data: responseData
});
log(`Response body:\n${b}`);
var u = require("url").parse(event.ResponseURL);
var r = require("https").request(
{
hostname: u.hostname,
port: 443,
path: u.path,
method: "PUT",
headers: {
"content-type": "",
"content-length": b.length
}
}, (p) => {
log(`Status code: ${p.statusCode}`);
log(`Status message: ${p.statusMessage}`);
s(context.done());
});
r.on("error", (e) => {
log(`request failed: ${e}`);
f(context.done(e));
});
r.write(b);
r.end();
});
}
Outputs:
CertificateArn:
Description: The Amazon Resource Name (ARN) of an AWS Certificate Manager (ACM) certificate.
Value: !GetAtt AcmCertificateForHostedZone.CertificateArn
Export:
Name: !Join [':', [ !Ref 'AWS::StackName', CertificateArn ]]
RootVerificationRecordName:
Description: Name for root domain CNAME verification record
Value: !GetAtt AcmCertificateForHostedZone.RootVerificationRecordName
Export:
Name: !Join [':', [ !Ref 'AWS::StackName', RootVerificationRecordName ]]
RootVerificationRecordValue:
Description: Value for root domain name CNAME verification record
Value: !GetAtt AcmCertificateForHostedZone.RootVerificationRecordValue
Export:
Name: !Join [':', [ !Ref 'AWS::StackName', RootVerificationRecordValue ]]
WwwVerificationRecordName:
Description: Name for www domain name CNAME verification record
Value: !GetAtt AcmCertificateForHostedZone.WwwVerificationRecordName
Export:
Name: !Join [':', [ !Ref 'AWS::StackName', WwwVerificationRecordName ]]
WwwVerificationRecordValue:
Description: Value for www domain name CNAME verification record
Value: !GetAtt AcmCertificateForHostedZone.WwwVerificationRecordValue
Export:
Name: !Join [':', [ !Ref 'AWS::StackName', WwwVerificationRecordValue ]]

View File

@@ -1,61 +0,0 @@
AWSTemplateFormatVersion: '2010-09-09'
Description: Creates hosted zone & sets up records for the CloudFront URL.
Parameters:
RootDomainName:
Type: String
Default: privacy.sexy
Description: The root DNS name of the website e.g. privacy.sexy
AllowedPattern: (?!-)[a-zA-Z0-9-.]{1,63}(?<!-)
ConstraintDescription: Must be a valid root domain name
CertificateStackName:
Type: String
Default: privacysexy-certificate-stack
Description: Name of the certificate stack.
Resources:
DNSHostedZone:
Type: AWS::Route53::HostedZone
Properties:
Name: !Ref RootDomainName
HostedZoneConfig:
Comment: !Join ['', ['Hosted zone for ', !Ref RootDomainName]]
HostedZoneTags:
-
Key: Application
Value: privacy.sexy
CertificateValidationDNSRecords:
Type: AWS::Route53::RecordSetGroup
Properties:
HostedZoneId: !Ref DNSHostedZone
RecordSets:
-
Name:
Fn::ImportValue: !Join [':', [!Ref CertificateStackName, RootVerificationRecordName]]
Type: 'CNAME'
TTL: '60'
ResourceRecords:
- Fn::ImportValue: !Join [':', [!Ref CertificateStackName, RootVerificationRecordValue]]
-
Name:
Fn::ImportValue: !Join [':', [!Ref CertificateStackName, WwwVerificationRecordName]]
Type: 'CNAME'
TTL: '60'
ResourceRecords:
- Fn::ImportValue: !Join [':', [!Ref CertificateStackName, WwwVerificationRecordValue]]
Outputs:
DNSHostedZoneNameServers:
Description: Name servers to update in domain registrar.
Value: !Join [' ', !GetAtt DNSHostedZone.NameServers]
DNSHostedZoneId:
Description: The ID of the hosted zone that you want to create the record in.
Value: !Ref DNSHostedZone
Export:
Name: !Join [':', [ !Ref 'AWS::StackName', DNSHostedZoneId ]]

View File

@@ -1,496 +0,0 @@
AWSTemplateFormatVersion: '2010-09-09'
Description: |-
> Deploys the identity management for the deployment
# Granulatiy cheatsheet: https://iam.cloudonaut.io/
Parameters:
WebStackName:
Type: String
Default: privacysexy-web-stack
Description: Name of the web stack.
DnsStackName:
Type: String
Default: privacysexy-dns-stack
Description: Name of the DNS stack.
CertificateStackName:
Type: String
Default: privacysexy-certificate-stack
Description: Name of the IAM stack.
Resources:
# -----------------------------
# ------ User & Group ---------
# -----------------------------
DeploymentGroup:
Type: AWS::IAM::Group
Properties:
# GroupName: No hardcoded naming because of easier CloudFormation management
ManagedPolicyArns:
- !Ref AllowValidateTemplatePolicy
DeploymentUser:
Type: AWS::IAM::User
Properties:
# # UserName: No hardcoded naming because of easier CloudFormation management
# # Policies: Assing policies on group level
Tags:
-
Key: Application
Value: privacy.sexy
AddDeploymentUserToDeploymentGroup:
Type: AWS::IAM::UserToGroupAddition
Properties:
GroupName: !Ref DeploymentGroup
Users:
- !Ref DeploymentUser
# -----------------------------
# ----------- Roles -----------
# -----------------------------
IamStackDeployRole:
Type: AWS::IAM::Role
Properties:
Description: Allows to deploy IAM stack
AssumeRolePolicyDocument:
Statement:
-
Effect: Allow
Principal:
AWS: !GetAtt DeploymentUser.Arn
Action: sts:AssumeRole
Tags:
-
Key: Application
Value: privacy.sexy
ManagedPolicyArns:
- !Ref CloudFormationDeployPolicy
- !Ref PolicyDeployPolicy
- !Ref IamStackDeployPolicy
CertificateStackDeployRole:
Type: AWS::IAM::Role
Properties:
Description: Allows to deploy certificate stack
AssumeRolePolicyDocument:
Statement:
-
Effect: Allow
Principal:
AWS: !GetAtt DeploymentUser.Arn
Action: sts:AssumeRole
Tags:
-
Key: Application
Value: privacy.sexy
ManagedPolicyArns:
- !Ref CloudFormationDeployPolicy
- !Ref LambdaBackedCustomResourceDeployPolicy
DnsStackDeployRole:
Type: AWS::IAM::Role
Properties:
Description: Allows to deploy DNS stack
AssumeRolePolicyDocument:
Statement:
-
Effect: Allow
Principal:
AWS: !GetAtt DeploymentUser.Arn
Action: sts:AssumeRole
Tags:
-
Key: Application
Value: privacy.sexy
ManagedPolicyArns:
- !Ref CloudFormationDeployPolicy
- !Ref DnsStackDeployPolicy
WebStackDeployRole:
Type: AWS::IAM::Role
Properties:
Description: Allows to deploy web stack
AssumeRolePolicyDocument:
Statement:
-
Effect: Allow
Principal:
AWS: !GetAtt DeploymentUser.Arn
Action: sts:AssumeRole
Tags:
-
Key: Application
Value: privacy.sexy
ManagedPolicyArns:
- !Ref CloudFormationDeployPolicy
- !Ref WebStackDeployPolicy
S3SiteDeployRole:
Type: 'AWS::IAM::Role'
Properties:
Description: "Allows to deploy website to S3"
AssumeRolePolicyDocument:
Statement:
-
Effect: Allow
Principal:
AWS: !GetAtt DeploymentUser.Arn
Action: sts:AssumeRole
Tags:
-
Key: Application
Value: privacy.sexy
ManagedPolicyArns:
- !Ref S3SiteDeployPolicy
- !Ref StackExportReaderPolicy
CloudFrontSiteDeployRole:
Type: 'AWS::IAM::Role'
Properties:
Description: "Allows to informs to CloudFront to renew its cache from S3"
AssumeRolePolicyDocument:
Statement:
-
Effect: Allow
Principal:
AWS: !GetAtt DeploymentUser.Arn
Action: sts:AssumeRole
Tags:
-
Key: Application
Value: privacy.sexy
ManagedPolicyArns:
- !Ref CloudFrontInvalidationPolicy
- !Ref StackExportReaderPolicy
ResolveCertificateLambdaRole: # See certificate stack
Type: AWS::IAM::Role
Properties:
Description: Allow deployment of certificates
AssumeRolePolicyDocument:
Statement:
-
Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- !Ref CertificateDeployPolicy
# --------------------------------
# ----------- Policies -----------
# --------------------------------
AllowValidateTemplatePolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: "No read & writes to resources, reveals just basic CloudFormation API to be used for validating templates"
# ManagedPolicyName: No hardcoded naming because of easier CloudFormation management
PolicyDocument:
Version: 2012-10-17
Statement:
-
Sid: AllowCloudFormationTemplateValidation
Effect: Allow
Action:
- cloudformation:ValidateTemplate
Resource: '*'
CloudFormationDeployPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: "Allows deploying CloudFormation using CLI command 'aws cloudformation deploy' (with change sets)"
# ManagedPolicyName: No hardcoded naming because of easier CloudFormation management
PolicyDocument:
Version: 2012-10-17
Statement:
-
Sid: AllowCloudFormationStackOperations
Effect: Allow
Action:
- cloudformation:GetTemplateSummary
- cloudformation:DescribeStacks
- cloudformation:CreateChangeSet
- cloudformation:ExecuteChangeSet
- cloudformation:DescribeChangeSet
Resource:
- !Sub arn:aws:cloudformation:*:${AWS::AccountId}:stack/${WebStackName}/*
- !Sub arn:aws:cloudformation:*:${AWS::AccountId}:stack/${DnsStackName}/*
- !Sub arn:aws:cloudformation:*:${AWS::AccountId}:stack/${AWS::StackName}/*
- !Sub arn:aws:cloudformation:*:${AWS::AccountId}:stack/${CertificateStackName}/*
IamStackDeployPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: Allows deploying IAM CloudFormation stack.
# ManagedPolicyName: No hardcoded naming because of easier CloudFormation management
PolicyDocument:
Version: 2012-10-17
Statement:
-
Sid: AllowUserArnExport
Effect: Allow
Action:
- iam:GetUser
Resource:
- !GetAtt DeploymentUser.Arn
-
Sid: AllowTagging
Effect: Allow
Action:
- iam:TagResource
Resource:
- !Sub arn:aws:cloudformation::${AWS::AccountId}:stack/${AWS::StackName}/*
- !GetAtt DeploymentUser.Arn
-
Sid: AllowRoleDeployment
Effect: Allow
Action:
- iam:CreateRole
Resource:
- !Sub arn:aws:iam::${AWS::AccountId}:role/${AWS::StackName}-*
LambdaBackedCustomResourceDeployPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: Allows deploying a lambda-backed custom resource.
# ManagedPolicyName: # ManagedPolicyName: No hardcoded naming because of easier CloudFormation management
PolicyDocument:
Version: 2012-10-17
Statement:
-
Sid: AllowLambdaDeployment
Effect: Allow
Action:
- lambda:GetFunction
- lambda:DeleteFunction
- lambda:CreateFunction
- lambda:GetFunctionConfiguration
- lambda:InvokeFunction
Resource:
- !Sub arn:aws:lambda:*:${AWS::AccountId}:function:${CertificateStackName}*
-
Sid: AllowPassingLambdaRole
Effect: Allow
Action:
- iam:PassRole
Resource:
- !GetAtt ResolveCertificateLambdaRole.Arn
CertificateDeployPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: Allows deploying certifications stack.
# ManagedPolicyName: # ManagedPolicyName: No hardcoded naming because of easier CloudFormation management
PolicyDocument:
Version: 2012-10-17
Statement:
-
Sid: AllowCertificateDeployment
Effect: Allow
Action:
- acm:RequestCertificate
- acm:DescribeCertificate
- acm:DeleteCertificate
- acm:AddTagsToCertificate
- acm:ListCertificates
Resource: '*' # Certificate Manager does not support resource level IAM
PolicyDeployPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: Allows deployment of policies
# ManagedPolicyName: Commented out because CloudFormation requires to rename when replacing custom-named resource
PolicyDocument:
Version: 2012-10-17
Statement:
-
Sid: AllowPolicyUpdates
Effect: Allow
Action:
- iam:ListPolicyVersions
- iam:CreatePolicyVersion
- iam:DeletePolicyVersion
- iam:CreatePolicy
- iam:DeletePolicy
- iam:GetPolicy
Resource:
- !Sub arn:aws:iam::${AWS::AccountId}:policy/${AWS::StackName}-* # when ManagedPolicyName is not given policies get name like StackName-*
-
Sid: AllowPoliciesOnRoles
Effect: Allow
Action:
- iam:AttachRolePolicy
- iam:DetachRolePolicy
- iam:GetRole
Resource:
- !Sub arn:aws:iam::${AWS::AccountId}:role/${AWS::StackName}-*
-
Sid: AllowPolicyAssigmentToGroup
Effect: Allow
Action:
- iam:AttachGroupPolicy
- iam:DetachGroupPolicy
Resource:
- !GetAtt DeploymentGroup.Arn
-
Sid: AllowGettingGroupInformation
Effect: Allow
Action:
- iam:GetGroup
Resource: !Sub arn:aws:iam::${AWS::AccountId}:group/${DeploymentGroup}
DnsStackDeployPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: Allows deployment of DNS stack
PolicyDocument:
Version: 2012-10-17
Statement:
-
Sid: AllowHostedZoneDeployment
Effect: Allow
Action:
- route53:CreateHostedZone
- route53:ListQueryLoggingConfigs
- route53:DeleteHostedZone
- route53:GetChange
- route53:ChangeTagsForResource
- route53:GetHostedZone
- route53:ChangeResourceRecordSets
Resource: '*' # Does not support resource-level permissions https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/access-control-overview.html#access-control-manage-access-intro-resource-policies
WebStackDeployPolicy:
# We need a role to run s3:PutBucketPolicy, IAM users cannot run it. See https://stackoverflow.com/a/48551383
Type: AWS::IAM::ManagedPolicy
Properties:
Description: Allows deployment of web stack
PolicyDocument:
Version: 2012-10-17
Statement:
-
Sid: AllowCloudFrontOAIDeployment
Effect: Allow
Action:
- cloudfront:GetCloudFrontOriginAccessIdentity
- cloudfront:CreateCloudFrontOriginAccessIdentity
- cloudfront:GetCloudFrontOriginAccessIdentityConfig
- cloudfront:DeleteCloudFrontOriginAccessIdentity
Resource: '*' # Does not support resource-level permissions https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cf-api-permissions-ref.html
-
Sid: AllowCloudFrontDistributionDeployment
Effect: Allow
Action:
- cloudfront:CreateDistribution
- cloudfront:DeleteDistribution
- cloudfront:UpdateDistribution
- cloudfront:GetDistribution
- cloudfront:TagResource
- cloudfront:UpdateCloudFrontOriginAccessIdentity
Resource: !Sub arn:aws:cloudfront::${AWS::AccountId}:*
-
Sid: AllowS3BucketPolicyAccess
Effect: Allow
Action:
- s3:CreateBucket
- s3:DeleteBucket
- s3:PutBucketWebsite
- s3:DeleteBucketPolicy
- s3:PutBucketPolicy
- s3:GetBucketPolicy
Resource: !Sub arn:aws:s3:::${WebStackName}*
-
Sid: AllowRecordDeploymentToRoute53
Effect: Allow
Action:
- route53:GetHostedZone
- route53:ChangeResourceRecordSets
- route53:GetChange
- route53:ListResourceRecordSets
Resource: '*' # Does not support resource-level permissions https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/access-control-overview.html#access-control-manage-access-intro-resource-policies
S3SiteDeployPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: Allows listing buckets to be able to list objects in a bucket
# ManagedPolicyName: Commented out because CloudFormation requires to rename when replacing custom-named resources
PolicyDocument:
Version: 2012-10-17
Statement:
-
Sid: AllowListingObjects
Effect: Allow
Action:
- s3:ListBucket # To allow ListObjectsV2
Resource: !Sub arn:aws:s3:::${WebStackName}*
-
Sid: AllowUpdatingObjects
Effect: Allow
Action:
- s3:PutObject
- s3:DeleteObject
Resource: !Sub arn:aws:s3:::${WebStackName}*/*
CloudFrontInvalidationPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: Allows creating invalidations on CloudFront
# ManagedPolicyName: Commented out because CloudFormation requires to rename when replacing custom-named resource
PolicyDocument:
Version: 2012-10-17
Statement:
-
Sid: AllowCloudFrontInvalidations
Effect: Allow
Action:
- cloudfront:CreateInvalidation
Resource: "*" # Does not support resource-level permissions https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cf-api-permissions-ref.html
StackExportReaderPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: Allows creating invalidations on CloudFront
# ManagedPolicyName: Commented out because CloudFormation requires to rename when replacing custom-named resource
PolicyDocument:
Version: 2012-10-17
Statement:
-
Sid: AllowGettingBucketName
Effect: Allow
Action:
- cloudformation:DescribeStacks
Resource: !Sub arn:aws:cloudformation:*:${AWS::AccountId}:stack/${WebStackName}/*
Outputs:
ResolveCertificateLambdaRoleArn:
Description: The Amazon Resource Name (ARN) of the lambda for deploying certificates.
Value: !GetAtt ResolveCertificateLambdaRole.Arn
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', ResolveCertificateLambdaRoleArn ] ]
CertificateStackDeployRoleArn:
Description: "GitHub secret: AWS_CERTIFICATE_STACK_DEPLOYMENT_ROLE_ARN"
Value: !GetAtt CertificateStackDeployRole.Arn
DnsStackDeployRoleArn:
Description: "GitHub secret: AWS_DNS_STACK_DEPLOYMENT_ROLE_ARN"
Value: !GetAtt DnsStackDeployRole.Arn
IamStackDeployRoleArn:
Description: "GitHub secret: AWS_IAM_STACK_DEPLOYMENT_ROLE_ARN"
Value: !GetAtt IamStackDeployRole.Arn
WebStackDeployRoleArn:
Description: "GitHub secret: AWS_WEB_STACK_DEPLOYMENT_ROLE_ARN"
Value: !GetAtt WebStackDeployRole.Arn
S3SiteDeployRoleArn:
Description: "GitHub secret: AWS_S3_SITE_DEPLOYMENT_ROLE_ARN"
Value: !GetAtt S3SiteDeployRole.Arn
CloudFrontSiteDeployRoleArn:
Description: "GitHub secret: AWS_CLOUDFRONT_SITE_DEPLOYMENT_ROLE_ARN"
Value: !GetAtt CloudFrontSiteDeployRole.Arn

View File

@@ -1,36 +0,0 @@
#!/bin/bash
# Parse parameters
while [[ "$#" -gt 0 ]]; do case $1 in
--user-profile) USER_PROFILE="$2"; shift;;
--role-profile) ROLE_PROFILE="$2"; shift;;
--role-arn) ROLE_ARN="$2"; shift;;
--session) SESSION="$2";shift;;
--region) REGION="$2";shift;;
*) echo "Unknown parameter passed: $1"; exit 1;;
esac; shift; done
# Verify parameters
if [ -z "$USER_PROFILE" ]; then echo "User profile name is not set."; exit 1; fi;
if [ -z "$ROLE_PROFILE" ]; then echo "Role profile name is not set."; exit 1; fi;
if [ -z "$ROLE_ARN" ]; then echo "Role ARN is not set"; exit 1; fi;
if [ -z "$SESSION" ]; then echo "Session name is not set."; exit 1; fi;
if [ -z "$REGION" ]; then echo "Region is not set."; exit 1; fi;
creds=$(aws sts assume-role --role-arn $ROLE_ARN --role-session-name $SESSION --profile $USER_PROFILE)
aws_access_key_id=$(echo $creds | jq -r '.Credentials.AccessKeyId')
echo ::add-mask::$aws_access_key_id
aws_secret_access_key=$(echo $creds | jq -r '.Credentials.SecretAccessKey')
echo ::add-mask::$aws_secret_access_key
aws_session_token=$(echo $creds | jq -r '.Credentials.SessionToken')
echo ::add-mask::$aws_session_token
aws configure --profile $ROLE_PROFILE set aws_access_key_id $aws_access_key_id
aws configure --profile $ROLE_PROFILE set aws_secret_access_key $aws_secret_access_key
aws configure --profile $ROLE_PROFILE set aws_session_token $aws_session_token
aws configure --profile $ROLE_PROFILE set region $REGION
echo Profile $ROLE_PROFILE is created
bash "${BASH_SOURCE%/*}/mask-identity.sh" --profile $ROLE_PROFILE

View File

@@ -1,25 +0,0 @@
#!/bin/bash
# Parse parameters
while [[ "$#" -gt 0 ]]; do case $1 in
--profile) PROFILE="$2"; shift;;
--access-key-id) ACCESS_KEY_ID="$2"; shift;;
--secret-access-key) SECRET_ACCESS_KEY="$2"; shift;;
--region) REGION="$2";shift;;
*) echo "Unknown parameter passed: $1"; exit 1;;
esac; shift; done
# Verify parameters
if [ -z "$PROFILE" ]; then echo "Profile name is not set."; exit 1; fi;
echo $PROFILE
if [ -z "$ACCESS_KEY_ID" ]; then echo "Access key ID is not set"; exit 1; fi;
if [ -z "$SECRET_ACCESS_KEY" ]; then echo "Secret access key is not set."; exit 1; fi;
if [ -z "$REGION" ]; then echo "Region is not set."; exit 1; fi;
aws configure --profile $PROFILE set aws_access_key_id $ACCESS_KEY_ID
aws configure --profile $PROFILE set aws_secret_access_key $SECRET_ACCESS_KEY
aws configure --profile $PROFILE set region $REGION
echo Profile $PROFILE is created
bash "${BASH_SOURCE%/*}/mask-identity.sh" --profile $PROFILE

View File

@@ -1,17 +0,0 @@
#!/bin/bash
# Parse parameters
while [[ "$#" -gt 0 ]]; do case $1 in
--profile) PROFILE="$2";shift;;
*) echo "Unknown parameter passed: $1"; exit 1;;
esac; shift; done
# Verify parameters
if [ -z "$PROFILE" ]; then echo "Profile name is not set."; exit 1; fi;
aws_identity=$(aws sts get-caller-identity --profile $PROFILE)
echo ::add-mask::$(echo $aws_identity | jq -r '.Account')
echo ::add-mask::$(echo $aws_identity | jq -r '.UserId')
echo ::add-mask::$(echo $aws_identity | jq -r '.Arn')
echo Credentials are masked

View File

@@ -1,43 +0,0 @@
#!/bin/bash
# Parse parameters
while [[ "$#" -gt 0 ]]; do case $1 in
--template-file) TEMPLATE_FILE="$2"; shift;;
--stack-name) STACK_NAME="$2"; shift;;
--profile) PROFILE="$2"; shift;;
--capabilities) CAPABILITY_IAM="$2"; shift;;
--role-arn) ROLE_ARN="$2";shift;;
--session) SESSION="$2";shift;;
--region) REGION="$2";shift;;
*) echo "Unknown parameter passed: $1"; exit 1;;
esac; shift; done
# Verify parameters
if [ -z "$TEMPLATE_FILE" ]; then echo "Template file is not set."; exit 1; fi;
if [ -z "$STACK_NAME" ]; then echo "Template file is not set."; exit 1; fi;
if [ -z "$PROFILE" ]; then echo "Profile is not set."; exit 1; fi;
if [ -z "$ROLE_ARN" ]; then echo "Role ARN is not set."; exit 1; fi;
if [ -z "$SESSION" ]; then echo "Role session is not set."; exit 1; fi;
echo Validating stack "$STACK_NAME"
aws cloudformation validate-template \
--template-body file://$TEMPLATE_FILE \
--profile $PROFILE
ROLE_PROFILE=$STACK_NAME
echo Assuming role
bash "${BASH_SOURCE%/*}/../configure/create-role-profile.sh" \
--role-profile $ROLE_PROFILE --user-profile $PROFILE \
--role-arn $ROLE_ARN \
--session $SESSION \
--region $REGION
echo Deploying stack "$TEMPLATE_FILE"
aws cloudformation deploy \
--template-file $TEMPLATE_FILE \
--stack-name $STACK_NAME \
${CAPABILITY_IAM:+ --capabilities $CAPABILITY_IAM} \
--no-fail-on-empty-changeset \
--profile $ROLE_PROFILE

View File

@@ -1,47 +0,0 @@
#!/bin/bash
# Parse parameters
while [[ "$#" -gt 0 ]]; do case $1 in
--folder) FOLDER="$2"; shift;;
--web-stack-name) WEB_STACK_NAME="$2"; shift;;
--web-stack-s3-name-output-name) WEB_STACK_S3_NAME_OUTPUT_NAME="$2"; shift;;
--storage-class) STORAGE_CLASS="$2"; shift;;
--profile) PROFILE="$2"; shift;;
--role-arn) ROLE_ARN="$2";shift;;
--session) SESSION="$2";shift;;
--region) REGION="$2";shift;;
*) echo "Unknown parameter passed: $1"; exit 1;;
esac; shift; done
# Verify parameters
if [ -z "$FOLDER" ]; then echo "Folder is not set."; exit 1; fi;
if [ -z "$PROFILE" ]; then echo "Profile is not set."; exit 1; fi;
if [ -z "$ROLE_ARN" ]; then echo "Role ARN is not set."; exit 1; fi;
if [ -z "$SESSION" ]; then echo "Role session is not set."; exit 1; fi;
if [ -z "$WEB_STACK_NAME" ]; then echo "Web stack name is not set."; exit 1; fi;
if [ -z "$WEB_STACK_S3_NAME_OUTPUT_NAME" ]; then echo "S3 name output name is not set."; exit 1; fi;
if [ -z "$STORAGE_CLASS" ]; then echo "S3 object storage class is not set."; exit 1; fi;
echo Assuming role
ROLE_PROFILE=deploy-s3
bash "${BASH_SOURCE%/*}/../configure/create-role-profile.sh" \
--role-profile $ROLE_PROFILE --user-profile $PROFILE \
--role-arn $ROLE_ARN \
--session $SESSION \
--region $REGION
echo Getting S3 bucket name from stack "$WEB_STACK_NAME" with output "$WEB_STACK_S3_NAME_OUTPUT_NAME"
S3_BUCKET_NAME=$(aws cloudformation describe-stacks \
--stack-name $WEB_STACK_NAME \
--query "Stacks[0].Outputs[?OutputKey=='$WEB_STACK_S3_NAME_OUTPUT_NAME'].OutputValue" \
--output text \
--profile $ROLE_PROFILE)
if [ -z "$S3_BUCKET_NAME" ]; then echo "Could not read S3 bucket name"; exit 1; fi;
echo ::add-mask::$S3_BUCKET_NAME # Just being extra cautious
echo Syncing folder to S3
aws s3 sync $FOLDER s3://$S3_BUCKET_NAME \
--storage-class $STORAGE_CLASS \
--no-progress --follow-symlinks --delete \
--profile $ROLE_PROFILE

View File

@@ -1,45 +0,0 @@
#!/bin/bash
# Parse parameters
while [[ "$#" -gt 0 ]]; do case $1 in
--paths) PATHS="$2"; shift;;
--web-stack-name) WEB_STACK_NAME="$2"; shift;;
--web-stack-cloudfront-arn-output-name) WEB_STACK_CLOUDFRONT_ARN_OUTPUT_NAME="$2"; shift;;
--profile) PROFILE="$2"; shift;;
--role-arn) ROLE_ARN="$2";shift;;
--session) SESSION="$2";shift;;
--region) REGION="$2";shift;;
*) echo "Unknown parameter passed: $1"; exit 1;;
esac; shift; done
# Verify parameters
if [ -z "$PATHS" ]; then echo "Paths is not set."; exit 1; fi;
if [ -z "$PROFILE" ]; then echo "Profile is not set."; exit 1; fi;
if [ -z "$ROLE_ARN" ]; then echo "Role ARN is not set."; exit 1; fi;
if [ -z "$SESSION" ]; then echo "Role session is not set."; exit 1; fi;
if [ -z "$WEB_STACK_NAME" ]; then echo "Web stack name is not set."; exit 1; fi;
if [ -z "$WEB_STACK_CLOUDFRONT_ARN_OUTPUT_NAME" ]; then echo "CloudFront ARN output name is not set."; exit 1; fi;
echo Assuming role
ROLE_PROFILE=invalidate-cloudfront
bash "${BASH_SOURCE%/*}/../configure/create-role-profile.sh" \
--role-profile $ROLE_PROFILE --user-profile $PROFILE \
--role-arn $ROLE_ARN \
--session $SESSION \
--region $REGION
echo Getting CloudFront ARN from stack "$WEB_STACK_NAME" with output "$WEB_STACK_CLOUDFRONT_ARN_OUTPUT_NAME"
CLOUDFRONT_ARN=$(aws cloudformation describe-stacks \
--stack-name $WEB_STACK_NAME \
--query "Stacks[0].Outputs[?OutputKey=='$WEB_STACK_CLOUDFRONT_ARN_OUTPUT_NAME'].OutputValue" \
--output text \
--profile $ROLE_PROFILE)
if [ -z "$CLOUDFRONT_ARN" ]; then echo "Could not read CloudFront ARN"; exit 1; fi;
echo :add-mask::$CLOUDFRONT_ARN
echo Syncing folder to S3
aws cloudfront create-invalidation \
--paths $PATHS \
--distribution-id $CLOUDFRONT_ARN \
--profile $ROLE_PROFILE

View File

@@ -1,138 +0,0 @@
AWSTemplateFormatVersion: '2010-09-09'
Description: |-
> Creates an S3 bucket configured for hosting a static webpage.
> Creates CloudFront distribution that has access to read the S3 bucket.
Parameters:
RootDomainName:
Type: String
Default: privacy.sexy
Description: The root DNS name of the website e.g. privacy.sexy
AllowedPattern: (?!-)[a-zA-Z0-9-.]{1,63}(?<!-)
ConstraintDescription: Must be a valid root domain name
CertificateStackName:
Type: String
Default: privacysexy-certificate-stack
Description: Name of the certificate stack.
DnsStackName:
Type: String
Default: privacysexy-dns-stack
Description: Name of the certificate stack.
PriceClass:
Type: String
Description: The CloudFront distribution price class
Default: 'PriceClass_100'
AllowedValues:
- 'PriceClass_100'
- 'PriceClass_200'
- 'PriceClass_All'
Resources:
S3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub ${AWS::StackName}-${RootDomainName} # Must have stack name for IAM to allow
WebsiteConfiguration:
IndexDocument: index.html
Tags:
-
Key: Application
Value: privacy.sexy
S3BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref S3Bucket
PolicyDocument: # Only used for CloudFront as it's the only way, otherwise use IAM roles in IAM stack.
Statement:
-
Sid: AllowCloudFrontRead
Action: s3:GetObject
Effect: Allow
Principal:
CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId
Resource: !Join ['', ['arn:aws:s3:::', !Ref S3Bucket, /*]]
CloudFrontOriginAccessIdentity:
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: !Sub 'CloudFront OAI for ${S3Bucket}'
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Comment: Cloudfront Distribution pointing to S3 bucket
Origins:
-
DomainName: !GetAtt S3Bucket.DomainName
Id: S3Origin
S3OriginConfig:
OriginAccessIdentity: !Sub "origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}"
Enabled: true
HttpVersion: 'http2'
DefaultRootObject: index.html
Aliases:
- !Ref RootDomainName
- !Sub 'www.${RootDomainName}'
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
Compress: true
TargetOriginId: S3Origin
ForwardedValues:
QueryString: true
Cookies:
Forward: none
ViewerProtocolPolicy: redirect-to-https
PriceClass: !Ref PriceClass
ViewerCertificate:
AcmCertificateArn:
# Certificate must be validated before it can be used here
Fn::ImportValue: !Join [':', [!Ref CertificateStackName, CertificateArn]]
SslSupportMethod: sni-only
MinimumProtocolVersion: TLSv1.1_2016
Tags:
-
Key: Application
Value: privacy.sexy
CloudFrontDNSRecords:
Type: AWS::Route53::RecordSetGroup
Properties:
HostedZoneId:
Fn::ImportValue: !Join [':', [!Ref DnsStackName, DNSHostedZoneId]]
RecordSets:
-
Name: !Ref RootDomainName
Type: A
AliasTarget:
DNSName: !GetAtt CloudFrontDistribution.DomainName
EvaluateTargetHealth: false
HostedZoneId: Z2FDTNDATAQYW2 # Static CloudFront distribution zone https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget.html#cfn-route53-aliastarget-hostedzoneid
-
Name: !Join ['', ['www.', !Ref RootDomainName]]
Type: A
AliasTarget:
DNSName: !GetAtt CloudFrontDistribution.DomainName
EvaluateTargetHealth: false
HostedZoneId: Z2FDTNDATAQYW2 # Static CloudFront distribution zone https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget.html#cfn-route53-aliastarget-hostedzoneid
Outputs:
CloudFrontDistributionArn: # Used by deployment script to be able to deploy to right S3 bucket
Description: Tthe Amazon Resource Name (ARN) of the CloudFront distribution.
Value: !Ref CloudFrontDistribution
S3BucketName: # Used by deployment script to be able to deploy to right S3 bucket
Description: Name of the S3 bucket.
Value: !Ref S3Bucket

5
build/README.md Normal file
View File

@@ -0,0 +1,5 @@
# build
- These are the file that are used by electron.
- Logos are created by from the [PNG icon](./../public/icon.png)
- by running `npx electron-icon-builder --input=./public/icon.png --output=build --flatten`

BIN
build/icons/1024x1024.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

BIN
build/icons/128x128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
build/icons/16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

BIN
build/icons/24x24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
build/icons/256x256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
build/icons/32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
build/icons/48x48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
build/icons/512x512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
build/icons/64x64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
build/icons/icon.icns Normal file

Binary file not shown.

BIN
build/icons/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 KiB

View File

@@ -1 +0,0 @@
<mxfile host="www.draw.io" modified="2019-12-30T13:07:22.931Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36" etag="vfXOAJJrIONaUEloGBPR" version="12.4.7" type="device"><diagram id="ymF_tBZ9P2_Wfw9L8arg" name="Page-1">5Vpbb9s2FP41AbYHC7zp9hjHdVqsGwpkQNu9FIxEy2xlUaWo2N6v36FEOZLlXNw6S7IpQUwd3j9+/M4hnTN6sdpcal4uf1epyM8ISjdndHZGSOQT+GsN29YwIRFqLZmWaWvDt4Yr+bdwxq5YLVNRDQoapXIjy6ExUUUhEjOwca3VelhsofJhryXPxMhwlfB8bP0oU7Ps5oVu7W+FzJZdzxi5nBXvCjtDteSpWvdM9M0ZvdBKmTa12lyI3ILX4dLWm9+RuxuYFoV5TAWx2swv/wy+49kff33Kok8f39/8NnHLc8Pz2k14JspcbStbsVhoXhldJ6bWwk3CbDtktKqLVNjG0RmdKm2WKlMFz98rVYIRg/GrMGbr1pTXRoFpaVa5y015tWzqdy8fuDFCF42FIGsdz9FNu1K1TsQ9E2OOK1xnwtxTDvttQZEOeOAgvBRqJYzeQgHH6AnyCGVBW8mRmro2tMi5kTdD5nBHwGzX1K71D0rCtHZN05B6FB4/ZiGNCQoHneAgxF7Mho22MLh2+mu/17QfIA8F8e0zbBrHyMOxH0IyojgkZNhLC+LDvezNWC0WlRjUgEQP01tTw9QjWEtHrJ3WMk+rRgdSoGqQw5pPrzWkMptqSQ3Za3FtR1iWuUxgrVTxH6V1eDSrEQuGhCPsNKxm2KMIIYZY5Ef0dJwOgsALaLij9HBPYqC71yN87B/J6adnMRux+FKat7Ul6HliuVmNyAk+pLRJuWrcVp92N0Ib4HR+nsvMcs1Yuu6s7/m1yD+oSjacp7NrZYxaQYHcZkx58i1riH+hcqWbvuiieaBI09l5Vbbu1e4K3r0s5MZSferGM1saY/3yucWKzJO0YJ4Ez7yQsKW0l0CPZJ5yw+HD2iv7qYou6WPiMiaVSiTPJ5k0y/p6gknklUUGHS1knndjLFRhMViowlw5iA44RWeyQIjNY/YCCXwvYOj2YUNikcA59PVtPAADbG3LfizQGQ9tlgGZjmWOP2LOuwJeJEArGhHkyfKFOezHKhE4IhoPEacnEqKYgl7c6QMp6IUfhjTwGWUhi+kPelofeywaNs380KMEiERiGuGXKEXBiFAXuarTuVZuTH0eqdrkshAXu4gbuV3YEw/4mdv+p5nmqRSDvJj5sznp5c2kFokTpsLycm+jQx3/nKKpD3aIR9U3cUimduxEj9G9w3LJ3VsCoxJ6yP5GZ9qtgclIdw6IYifWq01mz0YeX1fM06Jl0rvEjmcKr21qWCqx6C8a9J1E9+awG1zaww1abXA7gQAepDAdi154QPPCp5K8cMTQK3pyZgbonNLwOGaSMMQ4+N8ws6I2b+dJcNtae7C108tyXlUuDf5eJi59El5SL9p3xpH/vLyMRryciZvXH7eRB+O2QqwnW8H1xPImr128Og9ggeY90wTFvfDtTikb0fvUUZ0PJ5z+Q/d5FI95hA7wCD0Rj+IRj8q6qaXF91pUNgyQ1fhguxIQO6TNpVhz51RZ9J4j8IOV0dtProvm5bN98fzudbbpZ862D+nCg0dht/MePAqz4wJQOApTTIcyc6KjMEN0cLLA+xEo82gckBgHPmE4+tEINACZJHfGuYwhj4Uh8eMwBK8eBi8uGO2ufnt74fULKn1QUMVKfZUTbPnaqugXmGO5/VKtZC6295+BD8Qyu1GfQDztsexY9SQH1JM8lXpiPKLMmDPQjiwrC9d6KY24KnmjL2sIbvbiqWGEt1iIIEkORXhpGF8j9ATeiu0Dvne5hSPidRrVw5ziMeb0yTAff3cwjshfE+bB/ZhD/PnMgI+vvdmrBjxi9wJOGX1mwMc3tPg1A473g+ARw8eH/H8X8PHFZhfyViUvBsAH32v7zSkcMhvgzm14ll3/AkODX+ge9VK/2qT1hMhCOlm7udg6oS3XtdVF1e3ll9Ir9xVROwSYUjuKttCICT97+zCnjEVH3otNLzD1X+7tw8EY6afvxXYL82TXCqA9h/zriW4W7Al597V/GzXf/vMEffMP</diagram></mxfile>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

File diff suppressed because one or more lines are too long

BIN
img/architecture/gitops.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 KiB

BIN
img/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

8780
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,42 +1,71 @@
{
"name": "privacy.sexy",
"version": "0.1.0",
"version": "0.7.5",
"author": "undergroundwires",
"description": "Enforce privacy & security best-practices on Windows, because privacy is sexy 🍑🍆",
"homepage": "https://privacy.sexy",
"private": true,
"repository": {
"type": "git",
"url": "https://github.com/undergroundwires/privacy.sexy.git"
},
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"test:unit": "vue-cli-service test:unit"
"test:unit": "vue-cli-service test:unit",
"lint": "npm run lint:vue && npm run lint:yaml && npm run lint:md && npm run lint:md:relative-urls && npm run lint:md:consistency",
"electron:build": "vue-cli-service electron:build",
"electron:serve": "vue-cli-service electron:serve",
"lint:md": "markdownlint **/*.md --ignore node_modules",
"lint:md:consistency": "remark . --frail --use remark-preset-lint-consistent",
"lint:md:relative-urls": "remark . --frail --use remark-validate-links",
"lint:vue": "vue-cli-service lint --no-fix",
"lint:yaml": "yamllint **/*.yaml --ignore=node_modules/**/*.yaml",
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps"
},
"main": "background.js",
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.26",
"@fortawesome/free-brands-svg-icons": "^5.12.0",
"@fortawesome/free-regular-svg-icons": "^5.12.0",
"@fortawesome/free-solid-svg-icons": "^5.12.0",
"@fortawesome/vue-fontawesome": "^0.1.9",
"ace-builds": "^1.4.7",
"@fortawesome/fontawesome-svg-core": "^1.2.32",
"@fortawesome/free-brands-svg-icons": "^5.15.1",
"@fortawesome/free-regular-svg-icons": "^5.15.1",
"@fortawesome/free-solid-svg-icons": "^5.15.1",
"@fortawesome/vue-fontawesome": "^2.0.0",
"ace-builds": "^1.4.12",
"file-saver": "^2.0.2",
"inversify": "^5.0.1",
"liquor-tree": "^0.2.70",
"v-tooltip": "^2.0.2",
"vue": "^2.6.11",
"vue-class-component": "^7.1.0",
"vue-property-decorator": "^8.3.0"
"v-tooltip": "2.0.2",
"vue": "^2.6.12",
"vue-class-component": "^7.2.6",
"vue-js-modal": "^2.0.0-rc.6",
"vue-property-decorator": "^9.0.2"
},
"devDependencies": {
"@types/chai": "^4.2.7",
"@types/mocha": "^5.2.7",
"@types/ace": "0.0.42",
"@types/ace": "0.0.44",
"@types/chai": "^4.2.14",
"@types/file-saver": "^2.0.1",
"@vue/cli-plugin-typescript": "^4.1.1",
"@vue/cli-plugin-unit-mocha": "^4.1.1",
"@vue/cli-service": "^4.1.1",
"@vue/test-utils": "1.0.0-beta.30",
"@types/mocha": "^8.0.3",
"@vue/cli-plugin-typescript": "^4.5.7",
"@vue/cli-plugin-unit-mocha": "^4.5.7",
"@vue/cli-service": "^4.5.7",
"@vue/test-utils": "1.1.0",
"chai": "^4.2.0",
"sass": "^1.24.0",
"sass-loader": "^8.0.0",
"electron": "^10.1.3",
"electron-devtools-installer": "^3.1.1",
"electron-log": "^4.2.4",
"electron-updater": "^4.3.5",
"js-yaml-loader": "^1.2.2",
"typescript": "^3.7.4",
"vue-template-compiler": "^2.6.11"
"markdownlint-cli": "^0.24.0",
"remark-cli": "^9.0.0",
"remark-lint-no-dead-urls": "^1.1.0",
"remark-preset-lint-consistent": "^4.0.0",
"remark-validate-links": "^10.0.2",
"sass": "^1.27.0",
"sass-loader": "^10.0.3",
"typescript": "^4.0.3",
"vue-cli-plugin-electron-builder": "^2.0.0-rc.4",
"vue-template-compiler": "^2.6.12",
"yaml-lint": "^1.2.4"
}
}

BIN
public/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -32,4 +32,4 @@
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
</html>

2
public/robots.txt Normal file
View File

@@ -0,0 +1,2 @@
User-agent: *
Disallow:

View File

@@ -5,17 +5,16 @@
<TheSearchBar class="row" />
<TheScripts class="row"/>
<TheCodeArea class="row" theme="xcode" />
<TheCodeButtons class="row" />
<TheCodeButtons class="row code-buttons" />
<TheFooter />
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator';
import { ApplicationState, IApplicationState } from '@/application/State/ApplicationState';
import { Component, Vue } from 'vue-property-decorator';
import TheHeader from '@/presentation/TheHeader.vue';
import TheFooter from '@/presentation/TheFooter.vue';
import TheFooter from '@/presentation/TheFooter/TheFooter.vue';
import TheCodeArea from '@/presentation/TheCodeArea.vue';
import TheCodeButtons from '@/presentation/TheCodeButtons.vue';
import TheSearchBar from '@/presentation/TheSearchBar.vue';
@@ -67,6 +66,10 @@ body {
.row {
margin-bottom: 10px;
}
.code-buttons {
padding-bottom: 10px;
}
}
}

View File

@@ -0,0 +1,54 @@
import { OperatingSystem } from '@/domain/OperatingSystem';
import { DetectorBuilder } from './DetectorBuilder';
import { IBrowserOsDetector } from './IBrowserOsDetector';
export class BrowserOsDetector implements IBrowserOsDetector {
private readonly detectors = BrowserDetectors;
public detect(userAgent: string): OperatingSystem {
if (!userAgent) {
return OperatingSystem.Unknown;
}
for (const detector of this.detectors) {
const os = detector.detect(userAgent);
if (os !== OperatingSystem.Unknown) {
return os;
}
}
return OperatingSystem.Unknown;
}
}
// Reference: https://github.com/keithws/browser-report/blob/master/index.js#L304
const BrowserDetectors =
[
define(OperatingSystem.KaiOS, (b) =>
b.mustInclude('KAIOS')),
define(OperatingSystem.ChromeOS, (b) =>
b.mustInclude('CrOS')),
define(OperatingSystem.BlackBerryOS, (b) =>
b.mustInclude('BlackBerry')),
define(OperatingSystem.BlackBerryTabletOS, (b) =>
b.mustInclude('RIM Tablet OS')),
define(OperatingSystem.BlackBerry, (b) =>
b.mustInclude('BB10')),
define(OperatingSystem.Android, (b) =>
b.mustInclude('Android').mustNotInclude('Windows Phone')),
define(OperatingSystem.Android, (b) =>
b.mustInclude('Adr').mustNotInclude('Windows Phone')),
define(OperatingSystem.iOS, (b) =>
b.mustInclude('like Mac OS X')),
define(OperatingSystem.Linux, (b) =>
b.mustInclude('Linux').mustNotInclude('Android').mustNotInclude('Adr')),
define(OperatingSystem.Windows, (b) =>
b.mustInclude('Windows').mustNotInclude('Windows Phone')),
define(OperatingSystem.WindowsPhone, (b) =>
b.mustInclude('Windows Phone')),
define(OperatingSystem.macOS, (b) =>
b.mustInclude('OS X').mustNotInclude('Android').mustNotInclude('like Mac OS X')),
];
function define(os: OperatingSystem, applyRules: (builder: DetectorBuilder) => DetectorBuilder): IBrowserOsDetector {
const builder = new DetectorBuilder(os);
applyRules(builder);
return builder.build();
}

View File

@@ -0,0 +1,53 @@
import { IBrowserOsDetector } from './IBrowserOsDetector';
import { OperatingSystem } from '@/domain/OperatingSystem';
export class DetectorBuilder {
private readonly existingPartsInUserAgent = new Array<string>();
private readonly notExistingPartsInUserAgent = new Array<string>();
constructor(private readonly os: OperatingSystem) { }
public mustInclude(str: string): DetectorBuilder {
return this.add(str, this.existingPartsInUserAgent);
}
public mustNotInclude(str: string): DetectorBuilder {
return this.add(str, this.notExistingPartsInUserAgent);
}
public build(): IBrowserOsDetector {
if (!this.existingPartsInUserAgent.length) {
throw new Error('Must include at least a part');
}
return {
detect: (agent) => this.detect(agent),
};
}
private detect(userAgent: string): OperatingSystem {
if (!userAgent) {
throw new Error('User agent is null or undefined');
}
if (this.existingPartsInUserAgent.some((part) => !userAgent.includes(part))) {
return OperatingSystem.Unknown;
}
if (this.notExistingPartsInUserAgent.some((part) => userAgent.includes(part))) {
return OperatingSystem.Unknown;
}
return this.os;
}
private add(part: string, array: string[]): DetectorBuilder {
if (!part) {
throw new Error('part is empty or undefined');
}
if (this.existingPartsInUserAgent.includes(part)) {
throw new Error(`part ${part} is already included as existing part`);
}
if (this.notExistingPartsInUserAgent.includes(part)) {
throw new Error(`part ${part} is already included as not existing part`);
}
array.push(part);
return this;
}
}

View File

@@ -0,0 +1,5 @@
import { OperatingSystem } from '@/domain/OperatingSystem';
export interface IBrowserOsDetector {
detect(userAgent: string): OperatingSystem;
}

View File

@@ -0,0 +1,80 @@
import { BrowserOsDetector } from './BrowserOs/BrowserOsDetector';
import { IBrowserOsDetector } from './BrowserOs/IBrowserOsDetector';
import { IEnvironment } from './IEnvironment';
import { OperatingSystem } from '@/domain/OperatingSystem';
interface IEnvironmentVariables {
readonly window: Window & typeof globalThis;
readonly process: NodeJS.Process;
readonly navigator: Navigator;
}
export class Environment implements IEnvironment {
public static readonly CurrentEnvironment: IEnvironment = new Environment({
window,
process,
navigator,
});
public readonly isDesktop: boolean;
public readonly os: OperatingSystem;
protected constructor(
variables: IEnvironmentVariables,
browserOsDetector: IBrowserOsDetector = new BrowserOsDetector()) {
if (!variables) {
throw new Error('variables is null or empty');
}
this.isDesktop = isDesktop(variables);
this.os = this.isDesktop ?
getDesktopOsType(getProcessPlatform(variables))
: browserOsDetector.detect(getUserAgent(variables));
}
}
function getUserAgent(variables: IEnvironmentVariables): string {
if (!variables.window || !variables.window.navigator) {
return undefined;
}
return variables.window.navigator.userAgent;
}
function getProcessPlatform(variables: IEnvironmentVariables): string {
if (!variables.process || !variables.process.platform) {
return undefined;
}
return variables.process.platform;
}
function getDesktopOsType(processPlatform: string): OperatingSystem {
// https://nodejs.org/api/process.html#process_process_platform
if (processPlatform === 'darwin') {
return OperatingSystem.macOS;
} else if (processPlatform === 'win32') {
return OperatingSystem.Windows;
} else if (processPlatform === 'linux') {
return OperatingSystem.Linux;
}
return OperatingSystem.Unknown;
}
function isDesktop(variables: IEnvironmentVariables): boolean {
// More: https://github.com/electron/electron/issues/2288
// Renderer process
if (variables.window
&& variables.window.process
&& variables.window.process.type === 'renderer') {
return true;
}
// Main process
if (variables.process
&& variables.process.versions
&& Boolean(variables.process.versions.electron)) {
return true;
}
// Detect the user agent when the `nodeIntegration` option is set to true
if (variables.navigator
&& variables.navigator.userAgent
&& variables.navigator.userAgent.includes('Electron')) {
return true;
}
return false;
}

View File

@@ -0,0 +1,6 @@
import { OperatingSystem } from '@/domain/OperatingSystem';
export interface IEnvironment {
isDesktop: boolean;
os: OperatingSystem;
}

View File

@@ -1,21 +1,40 @@
import { Category } from '../../domain/Category';
import { Application } from '../../domain/Application';
import applicationFile from 'js-yaml-loader!./../application.yaml';
import { Category } from '@/domain/Category';
import { Application } from '@/domain/Application';
import { IApplication } from '@/domain/IApplication';
import { IProjectInformation } from '@/domain/IProjectInformation';
import { ApplicationYaml } from 'js-yaml-loader!./../application.yaml';
import { parseCategory } from './CategoryParser';
import { ProjectInformation } from '../../domain/ProjectInformation';
export function parseApplication(): Application {
export function parseApplication(content: ApplicationYaml, env: NodeJS.ProcessEnv = process.env): IApplication {
validate(content);
const categories = new Array<Category>();
if (!applicationFile.actions || applicationFile.actions.length <= 0) {
throw new Error('Application does not define any action');
}
for (const action of applicationFile.actions) {
for (const action of content.actions) {
const category = parseCategory(action);
categories.push(category);
}
const info = readAppInformation(env);
const app = new Application(
applicationFile.name,
applicationFile.repositoryUrl,
applicationFile.version,
info,
categories);
return app;
}
function readAppInformation(environment): IProjectInformation {
return new ProjectInformation(
environment.VUE_APP_NAME,
environment.VUE_APP_VERSION,
environment.VUE_APP_REPOSITORY_URL,
environment.VUE_APP_HOMEPAGE_URL,
);
}
function validate(content: ApplicationYaml): void {
if (!content) {
throw new Error('application is null or undefined');
}
if (!content.actions || content.actions.length <= 0) {
throw new Error('application does not define any action');
}
}

View File

@@ -1,20 +1,18 @@
import { YamlCategory, YamlScript } from 'js-yaml-loader!./application.yaml';
import { Script } from '@/domain/Script';
import { Category } from '../../domain/Category';
import { Category } from '@/domain/Category';
import { parseDocUrls } from './DocumentationParser';
import { parseScript } from './ScriptParser';
let categoryIdCounter: number = 0;
interface ICategoryChildren {
subCategories: Category[];
subScripts: Script[];
}
export function parseCategory(category: YamlCategory): Category {
if (!category.children || category.children.length <= 0) {
throw Error('Category has no children');
}
ensureValid(category);
const children: ICategoryChildren = {
subCategories: new Array<Category>(),
subScripts: new Array<Script>(),
@@ -31,6 +29,18 @@ export function parseCategory(category: YamlCategory): Category {
);
}
function ensureValid(category: YamlCategory) {
if (!category) {
throw Error('category is null or undefined');
}
if (!category.children || category.children.length === 0) {
throw Error('category has no children');
}
if (!category.category || category.category.length === 0) {
throw Error('category has no name');
}
}
function parseCategoryChild(
categoryOrScript: any, children: ICategoryChildren, parent: YamlCategory) {
if (isCategory(categoryOrScript)) {
@@ -38,19 +48,14 @@ function parseCategoryChild(
children.subCategories.push(subCategory);
} else if (isScript(categoryOrScript)) {
const yamlScript = categoryOrScript as YamlScript;
const script = new Script(
/* name */ yamlScript.name,
/* code */ yamlScript.code,
/* docs */ parseDocUrls(yamlScript),
/* is recommended? */ yamlScript.recommend);
const script = parseScript(yamlScript);
children.subScripts.push(script);
} else {
throw new Error(`Child element is neither a category or a script.
Parent: ${parent.category}, element: ${categoryOrScript}`);
Parent: ${parent.category}, element: ${JSON.stringify(categoryOrScript)}`);
}
}
function isScript(categoryOrScript: any): boolean {
return categoryOrScript.code && categoryOrScript.code.length > 0;
}

View File

@@ -1,34 +1,46 @@
import { YamlDocumentable } from 'js-yaml-loader!./application.yaml';
import { YamlDocumentable, DocumentationUrls } from 'js-yaml-loader!./application.yaml';
export function parseDocUrls(documentable: YamlDocumentable): ReadonlyArray<string> {
if (!documentable) {
throw new Error('documentable is null or undefined');
}
const docs = documentable.docs;
if (!docs) {
if (!docs || !docs.length) {
return [];
}
const result = new DocumentationUrls();
if (docs instanceof Array) {
for (const doc of docs) {
if (typeof doc !== 'string') {
throw new Error('Docs field (documentation url) must be an array of strings');
}
result.add(doc);
}
} else if (typeof docs === 'string') {
result.add(docs);
} else {
throw new Error('Docs field (documentation url) must a string or array of strings');
}
let result = new DocumentationUrlContainer();
result = addDocs(docs, result);
return result.getAll();
}
class DocumentationUrls {
function addDocs(docs: DocumentationUrls, urls: DocumentationUrlContainer): DocumentationUrlContainer {
if (docs instanceof Array) {
urls.addUrls(docs);
} else if (typeof docs === 'string') {
urls.addUrl(docs);
} else {
throw new Error('Docs field (documentation url) must a string or array of strings');
}
return urls;
}
class DocumentationUrlContainer {
private readonly urls = new Array<string>();
public add(url: string) {
public addUrl(url: string) {
validateUrl(url);
this.urls.push(url);
}
public addUrls(urls: any[]) {
for (const url of urls) {
if (typeof url !== 'string') {
throw new Error('Docs field (documentation url) must be an array of strings');
}
this.addUrl(url);
}
}
public getAll(): ReadonlyArray<string> {
return this.urls;
}

View File

@@ -0,0 +1,16 @@
import { Script } from '@/domain/Script';
import { YamlScript } from 'js-yaml-loader!./application.yaml';
import { parseDocUrls } from './DocumentationParser';
export function parseScript(yamlScript: YamlScript): Script {
if (!yamlScript) {
throw new Error('script is null or undefined');
}
const script = new Script(
/* name */ yamlScript.name,
/* code */ yamlScript.code,
/* revertCode */ yamlScript.revertCode,
/* docs */ parseDocUrls(yamlScript),
/* isRecommended */ yamlScript.recommend);
return script;
}

View File

@@ -3,13 +3,15 @@ import { IUserFilter } from './Filter/IUserFilter';
import { ApplicationCode } from './Code/ApplicationCode';
import { UserSelection } from './Selection/UserSelection';
import { IUserSelection } from './Selection/IUserSelection';
import { AsyncLazy } from '../../infrastructure/Threading/AsyncLazy';
import { Signal } from '../../infrastructure/Events/Signal';
import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
import { Signal } from '@/infrastructure/Events/Signal';
import { parseApplication } from '../Parser/ApplicationParser';
import { IApplicationState } from './IApplicationState';
import { Script } from '../../domain/Script';
import { Application } from '../../domain/Application';
import { Script } from '@/domain/Script';
import { IApplication } from '@/domain/IApplication';
import { IApplicationCode } from './Code/IApplicationCode';
import applicationFile from 'js-yaml-loader!@/application/application.yaml';
import { SelectedScript } from '@/application/State/Selection/SelectedScript';
/** Mutatable singleton application state that's the single source of truth throughout the application */
export class ApplicationState implements IApplicationState {
@@ -20,7 +22,7 @@ export class ApplicationState implements IApplicationState {
/** Application instance with all scripts. */
private static instance = new AsyncLazy<IApplicationState>(() => {
const application = parseApplication();
const application = parseApplication(applicationFile);
const selectedScripts = new Array<Script>();
const state = new ApplicationState(application, selectedScripts);
return Promise.resolve(state);
@@ -33,13 +35,11 @@ export class ApplicationState implements IApplicationState {
private constructor(
/** Inner instance of the all scripts */
public readonly app: Application,
public readonly app: IApplication,
/** Initially selected scripts */
public readonly defaultScripts: Script[]) {
this.selection = new UserSelection(app, defaultScripts);
this.code = new ApplicationCode(this.selection, app.version.toString());
this.selection = new UserSelection(app, defaultScripts.map((script) => new SelectedScript(script, false)));
this.code = new ApplicationCode(this.selection, app.info.version);
this.filter = new UserFilter(app);
}
}
export { IApplicationState, IUserFilter };

View File

@@ -1,27 +1,38 @@
import { UserScriptGenerator } from './UserScriptGenerator';
import { IUserSelection } from './../Selection/IUserSelection';
import { CodeChangedEvent } from './Event/CodeChangedEvent';
import { CodePosition } from './Position/CodePosition';
import { ICodeChangedEvent } from './Event/ICodeChangedEvent';
import { SelectedScript } from '@/application/State/Selection/SelectedScript';
import { IUserSelection } from '@/application/State/Selection/IUserSelection';
import { UserScriptGenerator } from './Generation/UserScriptGenerator';
import { Signal } from '@/infrastructure/Events/Signal';
import { IApplicationCode } from './IApplicationCode';
import { IScript } from '@/domain/IScript';
import { IUserScriptGenerator } from './Generation/IUserScriptGenerator';
export class ApplicationCode implements IApplicationCode {
public readonly changed = new Signal<string>();
public readonly changed = new Signal<ICodeChangedEvent>();
public current: string;
private readonly generator: UserScriptGenerator;
private scriptPositions = new Map<SelectedScript, CodePosition>();
constructor(userSelection: IUserSelection, private readonly version: string) {
constructor(
userSelection: IUserSelection,
private readonly version: string,
private readonly generator: IUserScriptGenerator = new UserScriptGenerator()) {
if (!userSelection) { throw new Error('userSelection is null or undefined'); }
if (!version) { throw new Error('version is null or undefined'); }
this.generator = new UserScriptGenerator();
if (!generator) { throw new Error('generator is null or undefined'); }
this.setCode(userSelection.selectedScripts);
userSelection.changed.on((scripts) => {
this.setCode(scripts);
});
}
private setCode(scripts: ReadonlyArray<IScript>) {
this.current = scripts.length === 0 ? '' : this.generator.buildCode(scripts, this.version);
this.changed.notify(this.current);
private setCode(scripts: ReadonlyArray<SelectedScript>): void {
const oldScripts = Array.from(this.scriptPositions.keys());
const code = this.generator.buildCode(scripts, this.version);
this.current = code.code;
this.scriptPositions = code.scriptPositions;
const event = new CodeChangedEvent(code.code, oldScripts, code.scriptPositions);
this.changed.notify(event);
}
}

View File

@@ -0,0 +1,64 @@
import { ICodeChangedEvent } from './ICodeChangedEvent';
import { SelectedScript } from '../../Selection/SelectedScript';
import { IScript } from '@/domain/IScript';
import { ICodePosition } from '@/application/State/Code/Position/ICodePosition';
export class CodeChangedEvent implements ICodeChangedEvent {
public readonly code: string;
public readonly addedScripts: ReadonlyArray<IScript>;
public readonly removedScripts: ReadonlyArray<IScript>;
public readonly changedScripts: ReadonlyArray<IScript>;
private readonly scripts: Map<IScript, ICodePosition>;
constructor(
code: string,
oldScripts: ReadonlyArray<SelectedScript>,
scripts: Map<SelectedScript, ICodePosition>) {
ensureAllPositionsExist(code, Array.from(scripts.values()));
this.code = code;
const newScripts = Array.from(scripts.keys());
this.addedScripts = selectIfNotExists(newScripts, oldScripts);
this.removedScripts = selectIfNotExists(oldScripts, newScripts);
this.changedScripts = getChangedScripts(oldScripts, newScripts);
this.scripts = new Map<IScript, ICodePosition>();
scripts.forEach((position, selection) => {
this.scripts.set(selection.script, position);
});
}
public isEmpty(): boolean {
return this.scripts.size === 0;
}
public getScriptPositionInCode(script: IScript): ICodePosition {
return this.scripts.get(script);
}
}
function ensureAllPositionsExist(script: string, positions: ReadonlyArray<ICodePosition>) {
const totalLines = script.split(/\r\n|\r|\n/).length;
for (const position of positions) {
if (position.endLine > totalLines) {
throw new Error(`script end line (${position.endLine}) is out of range.` +
`(total code lines: ${totalLines}`);
}
}
}
function getChangedScripts(
oldScripts: ReadonlyArray<SelectedScript>,
newScripts: ReadonlyArray<SelectedScript>): ReadonlyArray<IScript> {
return newScripts
.filter((newScript) => oldScripts.find((oldScript) => oldScript.id === newScript.id
&& oldScript.revert !== newScript.revert ))
.map((selection) => selection.script);
}
function selectIfNotExists(
selectableContainer: ReadonlyArray<SelectedScript>,
test: ReadonlyArray<SelectedScript>) {
return selectableContainer
.filter((script) => !test.find((oldScript) => oldScript.id === script.id))
.map((selection) => selection.script);
}

View File

@@ -0,0 +1,11 @@
import { IScript } from '@/domain/IScript';
import { ICodePosition } from '@/application/State/Code/Position/ICodePosition';
export interface ICodeChangedEvent {
readonly code: string;
addedScripts: ReadonlyArray<IScript>;
removedScripts: ReadonlyArray<IScript>;
changedScripts: ReadonlyArray<IScript>;
isEmpty(): boolean;
getScriptPositionInCode(script: IScript): ICodePosition;
}

View File

@@ -4,8 +4,20 @@ const TotalFunctionSeparatorChars = 58;
export class CodeBuilder {
private readonly lines = new Array<string>();
// Returns current line starting from 0 (no lines), or 1 (have single line)
public get currentLine(): number {
return this.lines.length;
}
public appendLine(code?: string): CodeBuilder {
this.lines.push(code);
if (!code) {
this.lines.push('');
return this;
}
const lines = code.match(/[^\r\n]+/g);
for (const line of lines) {
this.lines.push(line);
}
return this;
}

View File

@@ -0,0 +1,7 @@
import { SelectedScript } from '@/application/State/Selection/SelectedScript';
import { ICodePosition } from '@/application/State/Code/Position/ICodePosition';
export interface IUserScript {
code: string;
scriptPositions: Map<SelectedScript, ICodePosition>;
}

View File

@@ -0,0 +1,7 @@
import { SelectedScript } from '@/application/State/Selection/SelectedScript';
import { IUserScript } from './IUserScript';
export interface IUserScriptGenerator {
buildCode(
selectedScripts: ReadonlyArray<SelectedScript>,
version: string): IUserScript;
}

View File

@@ -0,0 +1,68 @@
import { SelectedScript } from '@/application/State/Selection/SelectedScript';
import { IUserScriptGenerator } from './IUserScriptGenerator';
import { CodeBuilder } from './CodeBuilder';
import { ICodePosition } from '@/application/State/Code/Position/ICodePosition';
import { CodePosition } from '../Position/CodePosition';
import { IUserScript } from './IUserScript';
export const adminRightsScript = {
name: 'Ensure admin privileges',
code: 'fltmc >nul 2>&1 || (\n' +
' echo Administrator privileges are required.\n' +
' PowerShell Start -Verb RunAs \'%0\' 2> nul || (\n' +
' echo Right-click on the script and select "Run as administrator".\n' +
' pause & exit 1\n' +
' )\n' +
' exit 0\n' +
')',
};
export class UserScriptGenerator implements IUserScriptGenerator {
public buildCode(selectedScripts: ReadonlyArray<SelectedScript>, version: string): IUserScript {
if (!selectedScripts) { throw new Error('scripts is undefined'); }
if (!version) { throw new Error('version is undefined'); }
let scriptPositions = new Map<SelectedScript, ICodePosition>();
if (!selectedScripts.length) {
return { code: '', scriptPositions };
}
const builder = initializeCode(version);
for (const selection of selectedScripts) {
scriptPositions = appendSelection(selection, scriptPositions, builder);
}
const code = finalizeCode(builder);
return { code, scriptPositions };
}
}
function initializeCode(version: string): CodeBuilder {
return new CodeBuilder()
.appendLine('@echo off')
.appendCommentLine(`https://privacy.sexy — v${version}${new Date().toUTCString()}`)
.appendFunction(adminRightsScript.name, adminRightsScript.code)
.appendLine();
}
function finalizeCode(builder: CodeBuilder): string {
return builder.appendLine()
.appendLine('pause')
.appendLine('exit /b 0')
.toString();
}
function appendSelection(
selection: SelectedScript,
scriptPositions: Map<SelectedScript, ICodePosition>,
builder: CodeBuilder): Map<SelectedScript, ICodePosition> {
const startPosition = builder.currentLine + 1;
appendCode(selection, builder);
const endPosition = builder.currentLine - 1;
builder.appendLine();
scriptPositions.set(selection, new CodePosition(startPosition, endPosition));
return scriptPositions;
}
function appendCode(selection: SelectedScript, builder: CodeBuilder) {
const name = selection.revert ? `${selection.script.name} (revert)` : selection.script.name;
const scriptCode = selection.revert ? selection.script.revertCode : selection.script.code;
builder.appendFunction(name, scriptCode);
}

View File

@@ -1,6 +1,7 @@
import { ICodeChangedEvent } from './Event/ICodeChangedEvent';
import { ISignal } from '@/infrastructure/Events/ISignal';
export interface IApplicationCode {
readonly changed: ISignal<string>;
readonly changed: ISignal<ICodeChangedEvent>;
readonly current: string;
}

View File

@@ -0,0 +1,24 @@
import { ICodePosition } from './ICodePosition';
export class CodePosition implements ICodePosition {
public get totalLines(): number {
return this.endLine - this.startLine;
}
constructor(
public readonly startLine: number,
public readonly endLine: number) {
if (startLine < 0) {
throw new Error('Code cannot start in a negative line');
}
if (endLine < 0) {
throw new Error('Code cannot end in a negative line');
}
if (endLine === startLine) {
throw new Error('Empty code');
}
if (endLine < startLine) {
throw new Error('End line cannot be less than start line');
}
}
}

View File

@@ -0,0 +1,5 @@
export interface ICodePosition {
readonly startLine: number;
readonly endLine: number;
readonly totalLines: number;
}

View File

@@ -1,31 +0,0 @@
import { CodeBuilder } from './CodeBuilder';
import { Script } from '@/domain/Script';
const adminRightsScript = {
name: 'Ensure admin privileges',
code: 'fltmc >nul 2>&1 || (\n' +
' echo This batch script requires administrator privileges. Right-click on\n' +
' echo the script and select "Run as administrator".\n' +
' pause\n' +
' exit 1\n' +
')',
};
export class UserScriptGenerator {
public buildCode(scripts: ReadonlyArray<Script>, version: string): string {
if (!scripts) { throw new Error('scripts is undefined'); }
if (!scripts.length) { throw new Error('scripts are empty'); }
if (!version) { throw new Error('version is undefined'); }
const builder = new CodeBuilder()
.appendLine('@echo off')
.appendCommentLine(`https://privacy.sexy — v${version}${new Date().toUTCString()}`)
.appendFunction(adminRightsScript.name, adminRightsScript.code).appendLine();
for (const script of scripts) {
builder.appendFunction(script.name, script.code).appendLine();
}
return builder.appendLine()
.appendLine('pause')
.appendLine('exit /b 0')
.toString();
}
}

View File

@@ -1,5 +1,5 @@
import { IFilterResult } from './IFilterResult';
import { IScript } from '@/domain/Script';
import { IScript } from '@/domain/IScript';
import { ICategory } from '@/domain/ICategory';
export class FilterResult implements IFilterResult {

View File

@@ -2,6 +2,7 @@ import { IFilterResult } from './IFilterResult';
import { ISignal } from '@/infrastructure/Events/Signal';
export interface IUserFilter {
readonly currentFilter: IFilterResult | undefined;
readonly filtered: ISignal<IFilterResult>;
readonly filterRemoved: ISignal<void>;
setFilter(filter: string): void;

View File

@@ -1,14 +1,16 @@
import { IScript } from '@/domain/IScript';
import { FilterResult } from './FilterResult';
import { IFilterResult } from './IFilterResult';
import { Application } from '../../../domain/Application';
import { IApplication } from '@/domain/IApplication';
import { IUserFilter } from './IUserFilter';
import { Signal } from '@/infrastructure/Events/Signal';
export class UserFilter implements IUserFilter {
public readonly filtered = new Signal<IFilterResult>();
public readonly filterRemoved = new Signal<void>();
public currentFilter: IFilterResult | undefined;
constructor(private application: Application) {
constructor(private application: IApplication) {
}
@@ -16,23 +18,36 @@ export class UserFilter implements IUserFilter {
if (!filter) {
throw new Error('Filter must be defined and not empty. Use removeFilter() to remove the filter');
}
const filterLowercase = filter.toLocaleLowerCase();
const filteredScripts = this.application.getAllScripts().filter(
(script) =>
script.name.toLowerCase().includes(filter.toLowerCase()) ||
script.code.toLowerCase().includes(filter.toLowerCase()));
(script) => isScriptAMatch(script, filterLowercase));
const filteredCategories = this.application.getAllCategories().filter(
(script) => script.name.toLowerCase().includes(filter.toLowerCase()));
(category) => category.name.toLowerCase().includes(filterLowercase));
const matches = new FilterResult(
filteredScripts,
filteredCategories,
filter,
);
this.currentFilter = matches;
this.filtered.notify(matches);
}
public removeFilter(): void {
this.currentFilter = undefined;
this.filterRemoved.notify();
}
}
function isScriptAMatch(script: IScript, filterLowercase: string) {
if (script.name.toLowerCase().includes(filterLowercase)) {
return true;
}
if (script.code.toLowerCase().includes(filterLowercase)) {
return true;
}
if (script.revertCode) {
return script.revertCode.toLowerCase().includes(filterLowercase);
}
return false;
}

View File

@@ -1,14 +1,18 @@
import { SelectedScript } from './SelectedScript';
import { ISignal } from '@/infrastructure/Events/Signal';
import { IScript } from '@/domain/IScript';
export interface IUserSelection {
readonly changed: ISignal<ReadonlyArray<IScript>>;
readonly selectedScripts: ReadonlyArray<IScript>;
readonly changed: ISignal<ReadonlyArray<SelectedScript>>;
readonly selectedScripts: ReadonlyArray<SelectedScript>;
readonly totalSelected: number;
addSelectedScript(scriptId: string): void;
removeAllInCategory(categoryId: number): void;
addOrUpdateAllInCategory(categoryId: number, revert: boolean): void;
addSelectedScript(scriptId: string, revert: boolean): void;
addOrUpdateSelectedScript(scriptId: string, revert: boolean): void;
removeSelectedScript(scriptId: string): void;
selectOnly(scripts: ReadonlyArray<IScript>): void;
isSelected(script: IScript): boolean;
isSelected(scriptId: string): boolean;
selectAll(): void;
deselectAll(): void;
}

View File

@@ -0,0 +1,14 @@
import { BaseEntity } from '@/infrastructure/Entity/BaseEntity';
import { IScript } from '@/domain/IScript';
export class SelectedScript extends BaseEntity<string> {
constructor(
public readonly script: IScript,
public readonly revert: boolean,
) {
super(script.id);
if (revert && !script.canRevert()) {
throw new Error('cannot revert an irreversible script');
}
}
}

View File

@@ -1,18 +1,19 @@
import { SelectedScript } from './SelectedScript';
import { IApplication } from '@/domain/IApplication';
import { IUserSelection } from './IUserSelection';
import { InMemoryRepository } from '@/infrastructure/Repository/InMemoryRepository';
import { IScript } from '@/domain/Script';
import { IScript } from '@/domain/IScript';
import { Signal } from '@/infrastructure/Events/Signal';
import { IRepository } from '@/infrastructure/Repository/IRepository';
export class UserSelection implements IUserSelection {
public readonly changed = new Signal<ReadonlyArray<IScript>>();
private readonly scripts = new InMemoryRepository<string, IScript>();
public readonly changed = new Signal<ReadonlyArray<SelectedScript>>();
private readonly scripts: IRepository<string, SelectedScript>;
constructor(
private readonly app: IApplication,
/** Initially selected scripts */
selectedScripts: ReadonlyArray<IScript>) {
selectedScripts: ReadonlyArray<SelectedScript>) {
this.scripts = new InMemoryRepository<string, SelectedScript>();
if (selectedScripts && selectedScripts.length > 0) {
for (const script of selectedScripts) {
this.scripts.addItem(script);
@@ -20,28 +21,64 @@ export class UserSelection implements IUserSelection {
}
}
/** Add a script to users application */
public addSelectedScript(scriptId: string): void {
public removeAllInCategory(categoryId: number): void {
const category = this.app.findCategory(categoryId);
const scriptsToRemove = category.getAllScriptsRecursively()
.filter((script) => this.scripts.exists(script.id));
if (!scriptsToRemove.length) {
return;
}
for (const script of scriptsToRemove) {
this.scripts.removeItem(script.id);
}
this.changed.notify(this.scripts.getItems());
}
public addOrUpdateAllInCategory(categoryId: number, revert: boolean = false): void {
const category = this.app.findCategory(categoryId);
const scriptsToAddOrUpdate = category.getAllScriptsRecursively()
.filter((script) =>
!this.scripts.exists(script.id)
|| this.scripts.getById(script.id).revert !== revert,
);
if (!scriptsToAddOrUpdate.length) {
return;
}
for (const script of scriptsToAddOrUpdate) {
const selectedScript = new SelectedScript(script, revert);
this.scripts.addOrUpdateItem(selectedScript);
}
this.changed.notify(this.scripts.getItems());
}
public addSelectedScript(scriptId: string, revert: boolean): void {
const script = this.app.findScript(scriptId);
if (!script) {
throw new Error(`Cannot add (id: ${scriptId}) as it is unknown`);
}
this.scripts.addItem(script);
const selectedScript = new SelectedScript(script, revert);
this.scripts.addItem(selectedScript);
this.changed.notify(this.scripts.getItems());
}
public addOrUpdateSelectedScript(scriptId: string, revert: boolean): void {
const script = this.app.findScript(scriptId);
const selectedScript = new SelectedScript(script, revert);
this.scripts.addOrUpdateItem(selectedScript);
this.changed.notify(this.scripts.getItems());
}
/** Remove a script from users application */
public removeSelectedScript(scriptId: string): void {
this.scripts.removeItem(scriptId);
this.changed.notify(this.scripts.getItems());
}
public isSelected(script: IScript): boolean {
return this.scripts.exists(script);
public isSelected(scriptId: string): boolean {
return this.scripts.exists(scriptId);
}
/** Get users scripts based on his/her selections */
public get selectedScripts(): ReadonlyArray<IScript> {
public get selectedScripts(): ReadonlyArray<SelectedScript> {
return this.scripts.getItems();
}
@@ -51,8 +88,9 @@ export class UserSelection implements IUserSelection {
public selectAll(): void {
for (const script of this.app.getAllScripts()) {
if (!this.scripts.exists(script)) {
this.scripts.addItem(script);
if (!this.scripts.exists(script.id)) {
const selection = new SelectedScript(script, false);
this.scripts.addItem(selection);
}
}
this.changed.notify(this.scripts.getItems());
@@ -78,9 +116,11 @@ export class UserSelection implements IUserSelection {
.forEach((scriptId) => this.scripts.removeItem(scriptId));
}
// Select from unselected scripts
scripts
.filter((script) => !this.scripts.exists(script))
.forEach((script) => this.scripts.addItem(script));
const unselectedScripts = scripts.filter((script) => !this.scripts.exists(script.id));
for (const toSelect of unselectedScripts) {
const selection = new SelectedScript(toSelect, false);
this.scripts.addItem(selection);
}
this.changed.notify(this.scripts.getItems());
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
declare module 'js-yaml-loader!*' {
type CategoryOrScript = YamlCategory | YamlScript;
type DocumentationUrls = ReadonlyArray<string> | string;
export type CategoryOrScript = YamlCategory | YamlScript;
export type DocumentationUrls = ReadonlyArray<string> | string;
export interface YamlDocumentable {
docs?: DocumentationUrls;
@@ -9,6 +9,7 @@ declare module 'js-yaml-loader!*' {
export interface YamlScript extends YamlDocumentable {
name: string;
code: string;
revertCode: string;
recommend: boolean;
}
@@ -17,10 +18,7 @@ declare module 'js-yaml-loader!*' {
category: string;
}
interface ApplicationYaml {
name: string;
version: number;
repositoryUrl: string;
export interface ApplicationYaml {
actions: ReadonlyArray<YamlCategory>;
}

133
src/background.ts Normal file
View File

@@ -0,0 +1,133 @@
'use strict';
import { app, protocol, BrowserWindow, shell } from 'electron';
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib';
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer';
import path from 'path';
import { autoUpdater } from 'electron-updater';
import log from 'electron-log';
const isDevelopment = process.env.NODE_ENV !== 'production';
declare const __static: string; // https://github.com/electron-userland/electron-webpack/issues/172
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win: BrowserWindow | null;
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
{ scheme: 'app', privileges: { secure: true, standard: true } },
]);
// Setup logging
autoUpdater.logger = log; // https://www.electron.build/auto-update#debugging
log.transports.file.level = 'silly';
if (!process.env.IS_TEST) {
Object.assign(console, log.functions); // override console.log, console.warn etc.
}
function createWindow() {
// Create the browser window.
win = new BrowserWindow({
width: 1350,
height: 955,
webPreferences: {
// Use pluginOptions.nodeIntegration, leave this alone
// See https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration
nodeIntegration: (process.env
.ELECTRON_NODE_INTEGRATION as unknown) as boolean,
},
// https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/recipes.html#set-tray-icon
icon: path.join(__static, 'icon.png'),
});
win.setMenuBarVisibility(false);
configureExternalsUrlsOpenBrowser(win);
loadApplication(win);
win.on('closed', () => {
win = null;
});
}
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (win === null) {
createWindow();
}
});
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', async () => {
if (isDevelopment && !process.env.IS_TEST) {
// Install Vue Devtools
try {
await installExtension(VUEJS_DEVTOOLS);
} catch (e) {
console.error('Vue Devtools failed to install:', e.toString()); // tslint:disable-line:no-console
}
}
createWindow();
});
// See electron-builder issue "checkForUpdatesAndNotify updates but does not notify on Windows 10"
// https://github.com/electron-userland/electron-builder/issues/2700
// https://github.com/electron/electron/issues/10864
if (process.platform === 'win32') {
// https://docs.microsoft.com/en-us/windows/win32/shell/appid#how-to-form-an-application-defined-appusermodelid
app.setAppUserModelId('Undergroundwires.PrivacySexy');
}
// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', (data) => {
if (data === 'graceful-exit') {
app.quit();
}
});
} else {
process.on('SIGTERM', () => {
app.quit();
});
}
}
function loadApplication(window: BrowserWindow) {
if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
win.loadURL(process.env.WEBPACK_DEV_SERVER_URL as string);
if (!process.env.IS_TEST) {
win.webContents.openDevTools();
}
} else {
createProtocol('app');
// Load the index.html when not in development
win.loadURL('app://./index.html');
// tslint:disable-next-line:max-line-length
autoUpdater.checkForUpdatesAndNotify(); // https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/recipes.html#check-for-updates-in-background-js-ts
}
}
function configureExternalsUrlsOpenBrowser(window: BrowserWindow) {
window.webContents.on('new-window', (event, url) => { // handle redirect
if (url !== win.webContents.getURL()) {
event.preventDefault();
shell.openExternal(url);
}
});
}

View File

@@ -2,6 +2,7 @@ import { IEntity } from '../infrastructure/Entity/IEntity';
import { ICategory } from './ICategory';
import { IScript } from './IScript';
import { IApplication } from './IApplication';
import { IProjectInformation } from './IProjectInformation';
export class Application implements IApplication {
public get totalScripts(): number { return this.flattened.allScripts.length; }
@@ -10,23 +11,13 @@ export class Application implements IApplication {
private readonly flattened: IFlattenedApplication;
constructor(
public readonly name: string,
public readonly repositoryUrl: string,
public readonly version: number,
public readonly categories: ReadonlyArray<ICategory>) {
if (!name) { throw Error('Application has no name'); }
if (!repositoryUrl) { throw Error('Application has no repository url'); }
if (!version) { throw Error('Version cannot be zero'); }
this.flattened = flatten(categories);
if (this.flattened.allCategories.length === 0) {
throw new Error('Application must consist of at least one category');
}
if (this.flattened.allScripts.length === 0) {
throw new Error('Application must consist of at least one script');
}
if (this.flattened.allScripts.filter((script) => script.isRecommended).length === 0) {
throw new Error('Application must consist of at least one recommended script');
public readonly info: IProjectInformation,
public readonly actions: ReadonlyArray<ICategory>) {
if (!info) {
throw new Error('info is undefined');
}
this.flattened = flatten(actions);
ensureValid(this.flattened);
ensureNoDuplicates(this.flattened.allCategories);
ensureNoDuplicates(this.flattened.allScripts);
}
@@ -75,30 +66,50 @@ interface IFlattenedApplication {
allScripts: IScript[];
}
function flattenRecursive(
function ensureValid(application: IFlattenedApplication) {
if (!application.allCategories || application.allCategories.length === 0) {
throw new Error('Application must consist of at least one category');
}
if (!application.allScripts || application.allScripts.length === 0) {
throw new Error('Application must consist of at least one script');
}
if (application.allScripts.filter((script) => script.isRecommended).length === 0) {
throw new Error('Application must consist of at least one recommended script');
}
}
function flattenCategories(
categories: ReadonlyArray<ICategory>,
flattened: IFlattenedApplication) {
flattened: IFlattenedApplication): IFlattenedApplication {
if (!categories || categories.length === 0) {
return flattened;
}
for (const category of categories) {
flattened.allCategories.push(category);
if (category.scripts) {
for (const script of category.scripts) {
flattened.allScripts.push(script);
}
}
if (category.subCategories && category.subCategories.length > 0) {
flattenRecursive(
category.subCategories as ReadonlyArray<ICategory>,
flattened);
}
flattened = flattenScripts(category.scripts, flattened);
flattened = flattenCategories(category.subCategories, flattened);
}
return flattened;
}
function flattenScripts(
scripts: ReadonlyArray<IScript>,
flattened: IFlattenedApplication): IFlattenedApplication {
if (!scripts) {
return flattened;
}
for (const script of scripts) {
flattened.allScripts.push(script);
}
return flattened;
}
function flatten(
categories: ReadonlyArray<ICategory>): IFlattenedApplication {
const flattened: IFlattenedApplication = {
let flattened: IFlattenedApplication = {
allCategories: new Array<ICategory>(),
allScripts: new Array<IScript>(),
};
flattenRecursive(categories, flattened);
flattened = flattenCategories(categories, flattened);
return flattened;
}

View File

@@ -3,15 +3,7 @@ import { IScript } from './IScript';
import { ICategory } from './ICategory';
export class Category extends BaseEntity<number> implements ICategory {
private static validate(category: ICategory) {
if (!category.name) {
throw new Error('name is null or empty');
}
if ((!category.subCategories || category.subCategories.length === 0) &&
(!category.scripts || category.scripts.length === 0)) {
throw new Error('A category must have at least one sub-category or scripts');
}
}
private allSubScripts: ReadonlyArray<IScript> = undefined;
constructor(
id: number,
@@ -20,6 +12,27 @@ export class Category extends BaseEntity<number> implements ICategory {
public readonly subCategories?: ReadonlyArray<ICategory>,
public readonly scripts?: ReadonlyArray<IScript>) {
super(id);
Category.validate(this);
validateCategory(this);
}
public getAllScriptsRecursively(): readonly IScript[] {
return this.allSubScripts || (this.allSubScripts = parseScriptsRecursively(this));
}
}
function parseScriptsRecursively(category: ICategory): ReadonlyArray<IScript> {
return [
...category.scripts,
...category.subCategories.flatMap((c) => c.getAllScriptsRecursively()),
];
}
function validateCategory(category: ICategory) {
if (!category.name) {
throw new Error('undefined or empty name');
}
if ((!category.subCategories || category.subCategories.length === 0) &&
(!category.scripts || category.scripts.length === 0)) {
throw new Error('A category must have at least one sub-category or script');
}
}

View File

@@ -1,13 +1,12 @@
import { IScript } from '@/domain/IScript';
import { ICategory } from '@/domain/ICategory';
import { IProjectInformation } from './IProjectInformation';
export interface IApplication {
readonly name: string;
readonly repositoryUrl: string;
readonly version: number;
readonly categories: ReadonlyArray<ICategory>;
readonly info: IProjectInformation;
readonly totalScripts: number;
readonly totalCategories: number;
readonly actions: ReadonlyArray<ICategory>;
getRecommendedScripts(): ReadonlyArray<IScript>;
findCategory(categoryId: number): ICategory | undefined;

View File

@@ -7,6 +7,7 @@ export interface ICategory extends IEntity<number>, IDocumentable {
readonly name: string;
readonly subCategories?: ReadonlyArray<ICategory>;
readonly scripts?: ReadonlyArray<IScript>;
getAllScriptsRecursively(): ReadonlyArray<IScript>;
}
export { IEntity } from '../infrastructure/Entity/IEntity';

View File

@@ -0,0 +1,11 @@
import { OperatingSystem } from './OperatingSystem';
export interface IProjectInformation {
readonly name: string;
readonly version: string;
readonly repositoryUrl: string;
readonly homepage: string;
readonly feedbackUrl: string;
readonly releaseUrl: string;
readonly repositoryWebUrl: string;
getDownloadUrl(os: OperatingSystem): string;
}

View File

@@ -1,9 +1,11 @@
import { IEntity } from './../infrastructure/Entity/IEntity';
import { IEntity } from '../infrastructure/Entity/IEntity';
import { IDocumentable } from './IDocumentable';
export interface IScript extends IEntity<string>, IDocumentable {
readonly name: string;
readonly code: string;
readonly isRecommended: boolean;
readonly documentationUrls: ReadonlyArray<string>;
readonly code: string;
readonly revertCode: string;
canRevert(): boolean;
}

View File

@@ -0,0 +1,14 @@
export enum OperatingSystem {
macOS,
Windows,
Linux,
KaiOS,
ChromeOS,
BlackBerryOS,
BlackBerry,
BlackBerryTabletOS,
Android,
iOS,
WindowsPhone,
Unknown,
}

View File

@@ -0,0 +1,55 @@
import { IProjectInformation } from './IProjectInformation';
import { OperatingSystem } from './OperatingSystem';
export class ProjectInformation implements IProjectInformation {
public readonly repositoryWebUrl: string;
constructor(
public readonly name: string,
public readonly version: string,
public readonly repositoryUrl: string,
public readonly homepage: string,
) {
if (!name) {
throw new Error('name is undefined');
}
if (!version || +version <= 0) {
throw new Error('version should be higher than zero');
}
if (!repositoryUrl) {
throw new Error('repositoryUrl is undefined');
}
if (!homepage) {
throw new Error('homepage is undefined');
}
this.repositoryWebUrl = getWebUrl(this.repositoryUrl);
}
public getDownloadUrl(os: OperatingSystem): string {
return `${this.repositoryWebUrl}/releases/download/${this.version}/${getFileName(os, this.version)}`;
}
public get feedbackUrl(): string {
return `${this.repositoryWebUrl}/issues`;
}
public get releaseUrl(): string {
return `${this.repositoryWebUrl}/releases/tag/${this.version}`;
}
}
function getWebUrl(gitUrl: string) {
if (gitUrl.endsWith('.git')) {
return gitUrl.substring(0, gitUrl.length - 4);
}
return gitUrl;
}
function getFileName(os: OperatingSystem, version: string): string {
switch (os) {
case OperatingSystem.Linux:
return `privacy.sexy-${version}.AppImage`;
case OperatingSystem.macOS:
return `privacy.sexy-${version}.dmg`;
case OperatingSystem.Windows:
return `privacy.sexy-Setup-${version}.exe`;
default:
throw new Error(`Unsupported os: ${OperatingSystem[os]}`);
}
}

View File

@@ -2,44 +2,59 @@ import { BaseEntity } from '@/infrastructure/Entity/BaseEntity';
import { IScript } from './IScript';
export class Script extends BaseEntity<string> implements IScript {
private static ensureNoEmptyLines(name: string, code: string): void {
if (code.split('\n').some((line) => line.trim().length === 0)) {
throw Error(`Script has empty lines "${name}"`);
}
}
private static ensureCodeHasUniqueLines(name: string, code: string): void {
const lines = code.split('\n')
.filter((line) => this.mayBeUniqueLine(line));
if (lines.length === 0) {
return;
}
const duplicateLines = lines.filter((e, i, a) => a.indexOf(e) !== i);
if (duplicateLines.length !== 0) {
throw Error(`Duplicates detected in script "${name}":\n ${duplicateLines.join('\n')}`);
}
}
private static mayBeUniqueLine(codeLine: string): boolean {
const trimmed = codeLine.trim();
if (trimmed === ')' || trimmed === '(') { // "(" and ")" are used often in batch code
return false;
}
return true;
}
constructor(
public name: string,
public code: string,
public documentationUrls: ReadonlyArray<string>,
public isRecommended: boolean) {
public readonly name: string,
public readonly code: string,
public readonly revertCode: string,
public readonly documentationUrls: ReadonlyArray<string>,
public readonly isRecommended: boolean) {
super(name);
if (code == null || code.length === 0) {
throw new Error('Code is empty or null');
validateCode(name, code);
if (revertCode) {
validateCode(name, revertCode);
if (code === revertCode) {
throw new Error(`${name}: Code itself and its reverting code cannot be the same`);
}
}
Script.ensureCodeHasUniqueLines(name, code);
Script.ensureNoEmptyLines(name, code);
}
public canRevert(): boolean {
return Boolean(this.revertCode);
}
}
export { IScript } from './IScript';
function validateCode(name: string, code: string): void {
if (!code || code.length === 0) {
throw new Error(`Code of ${name} is empty or null`);
}
ensureCodeHasUniqueLines(name, code);
ensureNoEmptyLines(name, code);
}
function ensureNoEmptyLines(name: string, code: string): void {
if (code.split('\n').some((line) => line.trim().length === 0)) {
throw Error(`Script has empty lines "${name}"`);
}
}
function mayBeUniqueLine(codeLine: string): boolean {
const trimmed = codeLine.trim();
if (trimmed === ')' || trimmed === '(') { // "(" and ")" are used often in batch code
return false;
}
if (codeLine.startsWith(':: ') || codeLine.startsWith('REM ')) { // Is comment?
return false;
}
return true;
}
function ensureCodeHasUniqueLines(name: string, code: string): void {
const lines = code.split('\n')
.filter((line) => mayBeUniqueLine(line));
if (lines.length === 0) {
return;
}
const duplicateLines = lines.filter((e, i, a) => a.indexOf(e) !== i);
if (duplicateLines.length !== 0) {
throw Error(`Duplicates detected in script "${name}":\n ${duplicateLines.join('\n')}`);
}
}

View File

@@ -3,7 +3,9 @@ import { IEntity } from '../Entity/IEntity';
export interface IRepository<TKey, TEntity extends IEntity<TKey>> {
readonly length: number;
getItems(predicate?: (entity: TEntity) => boolean): TEntity[];
getById(id: TKey): TEntity | undefined;
addItem(item: TEntity): void;
addOrUpdateItem(item: TEntity): void;
removeItem(id: TKey): void;
exists(item: TEntity): boolean;
exists(id: TKey): boolean;
}

View File

@@ -16,16 +16,34 @@ export class InMemoryRepository<TKey, TEntity extends IEntity<TKey>> implements
return predicate ? this.items.filter(predicate) : this.items;
}
public getById(id: TKey): TEntity | undefined {
const items = this.getItems((entity) => entity.id === id);
if (!items.length) {
return undefined;
}
return items[0];
}
public addItem(item: TEntity): void {
if (!item) {
throw new Error('Item is null');
throw new Error('item is null or undefined');
}
if (this.exists(item)) {
if (this.exists(item.id)) {
throw new Error(`Cannot add (id: ${item.id}) as it is already exists`);
}
this.items.push(item);
}
public addOrUpdateItem(item: TEntity): void {
if (!item) {
throw new Error('item is null or undefined');
}
if (this.exists(item.id)) {
this.removeItem(item.id);
}
this.items.push(item);
}
public removeItem(id: TKey): void {
const index = this.items.findIndex((item) => item.id === id);
if (index === -1) {
@@ -34,8 +52,8 @@ export class InMemoryRepository<TKey, TEntity extends IEntity<TKey>> implements
this.items.splice(index, 1);
}
public exists(entity: TEntity): boolean {
const index = this.items.findIndex((item) => item.id === entity.id);
public exists(id: TKey): boolean {
const index = this.items.findIndex((item) => item.id === id);
return index !== -1;
}
}

View File

@@ -1,9 +1,18 @@
import fileSaver from 'file-saver';
export enum FileType {
BatchFile,
}
export class SaveFileDialog {
public static saveText(text: string, fileName: string): void {
this.saveBlob(text, 'text/plain;charset=utf-8', fileName);
public static saveFile(text: string, fileName: string, type: FileType): void {
const mimeType = this.mimeTypes.get(type);
this.saveBlob(text, mimeType, fileName);
}
private static readonly mimeTypes = new Map<FileType, string>([
// Some browsers (including firefox + IE) require right mime type
// otherwise they ignore extension and save the file as text.
[ FileType.BatchFile, 'application/bat' ], // https://en.wikipedia.org/wiki/Batch_file
]);
private static saveBlob(file: BlobPart, fileType: string, fileName: string): void {
try {

View File

@@ -1,6 +1,7 @@
import Vue from 'vue';
import App from './App.vue';
import { ApplicationBootstrapper } from './presentation/Bootstrapping/ApplicationBootstrapper';
import 'core-js/fn/array/flat-map'; // Here until Vue 3 & CLI v4 https://github.com/vuejs/vue-cli/issues/3834
new ApplicationBootstrapper()
.bootstrap(Vue);

View File

@@ -1,3 +1,4 @@
import { VModalBootstrapper } from './Modules/VModalBootstrapper';
import { TreeBootstrapper } from './Modules/TreeBootstrapper';
import { IconBootstrapper } from './Modules/IconBootstrapper';
import { VueConstructor, IVueBootstrapper } from './IVueBootstrapper';
@@ -19,6 +20,7 @@ export class ApplicationBootstrapper implements IVueBootstrapper {
new TreeBootstrapper(),
new VueBootstrapper(),
new TooltipBootstrapper(),
new VModalBootstrapper(),
];
}
}

View File

@@ -4,15 +4,20 @@ import { faGithub } from '@fortawesome/free-brands-svg-icons';
/** BRAND ICONS (PREFIX: fab) */
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
/** REGULAR ICONS (PREFIX: far) */
import { faFolderOpen, faFolder } from '@fortawesome/free-regular-svg-icons';
import { faFolderOpen, faFolder, faSmile } from '@fortawesome/free-regular-svg-icons';
/** SOLID ICONS (PREFIX: fas (default)) */
import { faTimes, faFileDownload, faCopy, faSearch, faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import { faTimes, faFileDownload, faCopy, faSearch, faInfoCircle, faUserSecret, faDesktop, faTag, faGlobe } from '@fortawesome/free-solid-svg-icons';
export class IconBootstrapper implements IVueBootstrapper {
public bootstrap(vue: VueConstructor): void {
library.add(
faGithub,
faUserSecret,
faSmile,
faDesktop,
faGlobe,
faTag,
faFolderOpen,
faFolder,
faTimes,

View File

@@ -0,0 +1,8 @@
import VModal from 'vue-js-modal';
import { VueConstructor, IVueBootstrapper } from './../IVueBootstrapper';
export class VModalBootstrapper implements IVueBootstrapper {
public bootstrap(vue: VueConstructor): void {
vue.use(VModal, { dynamic: true, injectModalsContainer: true });
}
}

Some files were not shown because too many files have changed in this diff Show More